CI/Deployment for JavaScript Azure Functions using Azure Pipelines

Maninderjit (Mani) Bindra
Microsoft Azure
Published in
15 min readMar 11, 2020

While working on a recent project we required to define the git branching strategy, define the CI/Deployment pipelines, and create the Azure Pipelines for JavaScript Azure Functions, given the requirements and constraints. Following are some of the key requirements and constraints:

  • There were 3 environments dev, UAT and prod where the Azure Functions App was to be deployed
  • There were going to be scheduled releases with 3 week sprints
  • It must be possible to work on features of the current sprint (dev environment), while in parallel hardening to be released features from the previous sprint (UAT environment), and also have the ability to in parallel work on production hotfixes
  • Some members of the development team were new Git, and had no experience with writing testable code
  • Code quality needs to be maintained prior to merging Pull Requests (PRs) into protected branches. If PR builds fail due to code quality checks failing, there should be enough details provided to enable the developer submitting the PR to rectify the issue. The Unit Testing framework to be used is jest.
  • There should be a way for developers to execute code quality checks similar to Azure Pipelines locally
  • Development into environments should be triggered manually, and it should be possible to easily change this to Continuous deployment

The details of Branching, Build and Release Strategy selected are covered in a separate child post here.

This post will focuses on pipelines needed to deploy the code to the development environment. The 3 pipelines discussed here in detail are:

  • PR_BUILD_PIPELINE : For code quality checks in Pull Requests
  • PUBLISH_RELEASE_ARTIFACTS : To create the release artifacts when code is merged into the protected branches
  • RELEASE_TO_ENVIRONMENT: To release published artifacts into the development environment

Before looking the pipelines in more detail let us look at the Sample application for our scenario.

Sample Function App and Repository

The function app we will be using is from the Microsoft docs described here.

In this post we will be using Git repositories and pipelines in Azure DevOps. The repository has also be replicated to the GitHub repository so that it can be accessed when going through this post

Repository Structure

  • The src folder will contain the source for function apps. There will be one folder under src folder for each function app. So in the case of our sample there will be only one folder under the source folder
  • For each function app folder the structure will follow the standard shown in Folder Structure. Final structure of src folder in our case is something like shown below
src
| - SampleFunctionApp
| | - HttpTrigger
| | | - index.js
| | | - index.test.js
| | | - function.json
| | - TimerTrigger
| | | - index.js
| | | - index.test.js
| | | - function.json
| | - host.json
| | - package.json
| | - azure-pipelines-pr-build.yml
| | - azure-pipelines-build-publish-artifacts.yml
| | - azure-pipelines-release-to-dev.yml

We will look at the 3 pipeline yaml files, and package.json in detail in the following sections. Let us look at some of the other key files.

  • The index.js files are the files containing the function code for the HttpTrigger and TimerTrigger functions in the SampleFunctionApp
  • The index.test.js files are the files containing the jest tests for the HttpTrigger and TimerTrigger functions in the SampleFunctionApp

Package.json and local execution of quality checks

The Package.json file contains the dev dependencies and the scripts we will be using to execute the tests / coverage locally and in Azure Pipelines. Local execution enables the developers to perform linting and other tests locally before creating the PR. Let us look at some of the key lines of the package.json file:

{
"name": "node-api-test",
"version": "1.0.0",
"description": "",
"scripts": {
"lint": "./node_modules/.bin/eslint ./",
"test": "jest",
"test-ci": "node_modules/.bin/jest --ci --reporters=jest-junit --reporters=default",
"coverage": "jest --coverage",
"coverage-ci": "node_modules/.bin/jest --ci --reporters=jest-junit --coverage --coverageReporters=cobertura"
},
"dependencies": {},
"devDependencies": {
"eslint": "6.8.0",
"jest": "^24.9.0",
"jest-junit": "^10.0.0"
}
}

The Dev dependencies section contains linting and unit test related dependencies. Let us see how we can run the linting, unit tests and coverage locally.

Linting

npm run-script lint or ./node_modules/.bin/eslint ./ can be executed by the developers locally to execute linting against the code. This is the same command which is executed in the PR Build and Publish Artifact pipelines as well.

Execution with linting (linting error introduced)

$ npm run-script lint

> node-api-test@1.0.0 lint /Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp
> eslint ./


/Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp/HttpTrigger/index.js
1:5 error 'newVar' is assigned a value but never used no-unused-vars

✖ 1 problem (1 error, 0 warnings)

Execution with no linting errors

$ npm run-script lint

> node-api-test@1.0.0 lint /Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp
> eslint ./

Unit Test Execution

To execute unit tests locally npm run-script test can be executed by the developers. npm run-script test-ci is executed by Azure Pipelines, which produce unit test executing reports in jUnit format which are then published in Azure DevOps.

Local unit test execution with errors (error introduced)

$ npm run-script test

> node-api-test@1.0.0 test /Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp
> jest

PASS TimerTrigger/index.test.js
FAIL HttpTrigger/index.test.js
● Http trigger should return known text

expect(received).toEqual(expected) // deep equality

Expected: "Hello Bill"
Received: "Hello-Bill"


11 |
12 | expect(context.log.mock.calls.length).toBe(1);
> 13 | expect(context.res.body).toEqual('Hello Bill');
| ^
14 | });

at Object.<anonymous>.test (HttpTrigger/index.test.js:13:30)

Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 1 passed, 2 total

Snapshots: 0 total
Time: 3.477s

Unit test execution without errors

$ npm run-script test

> node-api-test@1.0.0 test /Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp
> jest

PASS TimerTrigger/index.test.js
PASS HttpTrigger/index.test.js

Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total

Snapshots: 0 total
Time: 1.783s, estimated 2s
Ran all test suites.

Code Coverage

Similarly developers can execute npm run-script coverage locally to get details of the code coverage. npm run-script coverage-ci is executed by Azure Pipelines which produces coverage reports in a format compatible with Azure DevOps

$ npm run-script coverage

> node-api-test@1.0.0 coverage /Users/mani/dev/repos/hackfests/acc/javascript-azure-functions-ci-deployment/src/SampleFunctionApp
> jest --coverage

PASS HttpTrigger/index.test.js
PASS TimerTrigger/index.test.js
--------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------|----------|----------|----------|----------|-------------------|
All files | 83.33 | 44.44 | 100 | 83.33 | |
HttpTrigger | 80 | 42.86 | 100 | 80 | |
index.js | 80 | 42.86 | 100 | 80 |
11 |
TimerTrigger | 80 | 50 | 100 | 80 | |
index.js | 80 | 50 | 100 | 80 | 6 |

testing | 100 | 100 | 100 | 100 | |
defaultContext.js | 100 | 100 | 100 | 100 | |
defaultTimer.js | 100 | 100 | 100 | 100 | |
--------------------|----------|----------|----------|----------|-------------------|

Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.638s
Ran all test suites.

After the execution of this command coverage files are generated locally. You can navigate to coverage/lcov-report/index.html:

Viewing coverage reports locally

You can also drill down into each function to see file wise coverage and also understand which lines of a file are not covered.

HttpTrigger index.js coverage

We will also be publishing similar reports through our CI pipeline which will be visible in Azure DevOps.

Let us now look at Azure Pipelines in more detail starting with the PR Build pipeline

PR_BUILD_PIPELINE Pipeline

This pipeline is triggered each time a PR is created, or new commits are pushed against a PR branch.

Create Pipeline

We can create the pipeline in Azure DevOps by pointing to the azure-pipelines-pr-build.yml file

Create pipeline

Let us look first look at some of the key lines of this pipeline before looking at the whole file.

The pipeline trigger is set to none

trigger: none

PR build in Azure DevOps is configured using branch policy — build validation as shown later in this section.

The code coverage threshold, the node version and and the function app name are variables for this pipeline.

variables:
node_version: 12.14.1
function_app_name: 'SampleFunctionApp'
code_coverge_threshold: '60'

The first task installs the npm dependencies .

- script: npm install

Next we perform linting against the code.

# Perform linting using eslint  
- script: npm run-script lint

After that we execute the unit tests and publish the unit test report.

- script: npm run-script test-ci
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Execute unit tests"
continueOnError: true
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
testRunTitle: 'Jest Unit Tests'
failTaskOnFailedTests: true

Next we check unit test coverage and publish the coverage report in Azure DevOps:

- script: npm run-script coverage-ci- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: $(System.DefaultWorkingDirectory)/src/$(function_app_name)/coverage/cobertura-coverage.xml

Finally if the code coverage is below the threshold (set to 60% by default) we fail the build

- task: BuildQualityChecks@6
inputs:
checkCoverage: true
coverageFailOption: 'fixed'
coverageType: 'lines'
coverageThreshold: $(code_coverge_threshold)

Now let us take the look at the entire file:

trigger: none

variables:
node_version: 12.14.1
function_app_name: 'SampleFunctionApp'
code_coverge_threshold: '60'

pool:
vmImage: 'ubuntu-16.04'

steps:
- task: NodeTool@0
inputs:
versionSpec: $(node_version)

# Install dependencies
- script: npm install
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Install linting, test and coverage dependencies"

# Perform linting using eslint
- script: npm run-script lint
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Run code linting"

# Execute unit tests
- script: npm run-script test-ci
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Execute unit tests"
continueOnError: true


# Publish unit test results
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
testRunTitle: 'Jest Unit Tests'
failTaskOnFailedTests: true
displayName: 'Publish unit test report'

- script: npm run-script coverage-ci
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Calculate Code Coverage"

# Publish code coverage results
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: $(System.DefaultWorkingDirectory)/src/$(function_app_name)/coverage/cobertura-coverage.xml
displayName: "publish code coverage report"

# Fail build if coverage below threshold
- task: BuildQualityChecks@6
inputs:
checkCoverage: true
coverageFailOption: 'fixed'
coverageType: 'lines'
coverageThreshold: $(code_coverge_threshold)

Enable PR Build using Build validation

After the pipeline is created we can enable PR builds for develop branch by adding build validation in the branch policy for the develop branch.

Enable Build validation for PR Build

After this is enabled every PR targetting the develop branch will cause this build to execute.

Let us look at the following PR build execution scenarios

PR build failed : linting Error

First let us introduce a linting error as follows

unused variable addeed

We see that this causes the PR build to fail.

We can then drill down into the failed build and see the linting error

Unused Variable linting error.

PR build failed : Unit test execution error

To test this we inject unit testing error:

Unit testing error injected

The build fails, but in this case since the test report was published, we see the Tests tab at the top, we can go to the Tests tab and see the details of the failed tests.

Details of test failure visible in Azure DevOps

PR build failed : Code Cover below threshold (90% threshold)

To test this we will change the code coverage threshold in the yaml file to 90%.

Following change made to the pipeline YAML.

In this case the build fails but we see the Tests, Code Coverage and Extensions tabs at the top

Let us first look at the Tests Tab. We see that all tests succeeded.

Tests tab

Next, let’s look at the Extensions Tab. Here we see that the build failed because the code coverage was below the threshold.

Extension Tab

We can now go to the Code Coverage and then drill down and see which lines are not covered by the unit tests

Code Coverage Summary
Drill Down into index.js file

Successful PR Build

Finally Let us now look at a successful build. Every thing is similar to previous execution except that code coverage threshold is now 60 %

As you can see all steps of the pipeline are successful and we can view the details of Tests and coverage going to the corresponding tabs.

Successful Execution

After the PR build is successful, Manual reviews can happen on the PR after which the code is merged into the protected branches like develop. Let us look at what happens when this code is merged into the develop branch

PUBLISH_RELEASE_ARTIFACTS Pipeline

This pipeline is triggered each time code is merged into a branch where we want to release to any of our environments. In this case this pipeline is triggered each time code is merged into develop, master or a release branch.

trigger:
- develop
- master
- release/*

Pipeline File: azure-pipelines-build-publish-artifacts.yml

Most of the steps of this pipeline are similar to the PR build pipeline (linting, unit testing, and coverage). The last few steps are additional, let us look at those in more detail before looking at the entire file

After the code checks are complete we remove the dev dependencies by calling npm prune -production. This is because we do not need the dev dependencies like jest when we actually deploy the code to the function app.

- script: npm prune --production

After this we create a zip archive which can be deployed to the function app

- task: ArchiveFiles@2
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)/src/$(function_app_name)"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"

Finally in this pipeline we publish the zip artifact created so that it can be fetched by the release pipeline which actually does the deployment to the function app.

- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
ArtifactName: $(function_app_name)

Now we can look at the entire file:

trigger:
- develop
- master
- release/*


variables:
node_version: 12.14.1
function_app_name: 'SampleFunctionApp'
code_coverge_threshold: '60'

pool:
vmImage: 'ubuntu-16.04'

steps:
- task: NodeTool@0
inputs:
versionSpec: $(node_version)

# Install dev dependencies
- script: npm install
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Install linting, test and coverage dependencies"

# Perform linting using eslint
- script: npm run-script lint
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Run code linting"

# Execute unit tests
- script: npm run-script test-ci
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Execute unit tests"
continueOnError: true


# Publish unit test results
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
testRunTitle: 'Jest Unit Tests'
failTaskOnFailedTests: true
displayName: 'Publish unit test report'

- script: npm run-script coverage-ci
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Calculate Code Coverage"

# Publish code coverage results
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: $(System.DefaultWorkingDirectory)/src/$(function_app_name)/coverage/cobertura-coverage.xml
displayName: "publish code coverage report"

# Fail build if coverage below threshold
- task: BuildQualityChecks@6
inputs:
checkCoverage: true
coverageFailOption: 'fixed'
coverageType: 'lines'
coverageThreshold: $(code_coverge_threshold)

# Prune dev dependencies
- script: npm prune --production
workingDirectory: $(System.DefaultWorkingDirectory)/src/$(function_app_name)
displayName: "Prune dev dependencies"

- task: ArchiveFiles@2

displayName: "Archive files"
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)/src/$(function_app_name)"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"

- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
ArtifactName: $(function_app_name)

The output of this pipeline is similar to the PR build pipeline, with one minor addition that we see an additional artifact being published.

Additional Artifact published

On drilling down we see that the SampleFunctionApp is published and available for other pipelines to use for deployment.

Build Artifact Published

In the next pipeline let us see how this published artifact is deployed.

RELEASE_TO_ENVIRONMENT Pipeline

This pipeline is used to deploy artifacts published by pipelines to an environment. In our case we will see how artifacts published from the development branch are deployed to a function app.

Pipeline File: azure-pipelines-release-to-dev.yml

Let us look at some of the key lines of this file before looking at the entire file.

Since we wanted manually triggered deployments we set the triggers to none

trigger: none

Let us look at the variables which can be used to configure the pipeline:

variables:
project_name: 'post-artifacts'
build_pipeline: 'Publish-Artifacts-JavaScript-AzureFunctions'
branch_name: 'refs/heads/develop'
build_id: ''
artifact_name: 'SampleFunctionApp'
azure_subscription: 'post-artifact-connection'
function_app_name: 'JavaScriptSampleFunctionApp'
  • project_name : This is the Azure DevOps project name under the Azure DevOps organization
  • build_pipeline: This is the pipeline from where we want to download the artifact to be deployed to the function app
  • branch_name: This is the branch against which we want to fetch the artifact to be deployed
  • build_id : If this is blank, we fetch the latest artifact for the build_pipeline against the branch. If build_id is specified then we fetcch the artifact from the specific build
  • artifact_name: Name of the published artifact which we want to download and deploy
  • azure_subscription: Name of the Azure service connection which has permissions to deploy the artifact zip to the function app
  • function_app_name: Name of the function app to which we want to deploy.

We can now look at the steps

STEPS

  • Download Artifact: In the first step we download the artifact to be deployed. If specific buildid is provided then we download the published artifact from that build, else we down the artifact from the latest build for the publish artifact pipeline against the specified (develop) branch
- task: DownloadBuildArtifacts@0
inputs:
buildType: 'specific'
project: $(project_name)
pipeline: $(build_pipeline)
# If build Id is not set, get latest build from branch else get specified build id.
${{ if eq(variables['build_id'], '') }}:
buildVersionToDownload: 'latestFromBranch'
${{ if ne(variables['build_id'], '') }}:
buildVersionToDownload: 'specific'
buildId: $(build_id)
branchName: $(branch_name)
downloadType: 'single'
artifactName: $(artifact_name)
downloadPath: '$(System.ArtifactsDirectory)'
  • Deploy zip artifact to Function App: In this step the downloaded artifact is deployed to the function app
- task: AzureFunctionApp@1
inputs:
azureSubscription: $(azure_subscription)
appType: 'functionApp'
appName: $(function_app_name)
package: '$(System.ArtifactsDirectory)/**/*.zip'
deploymentMethod: 'auto'

We can now look at the entire file:

trigger: none 

variables:
project_name: 'post-artifacts'
build_pipeline: 'Publish-Artifacts-JavaScript-AzureFunctions'
branch_name: 'refs/heads/develop'
build_id: ''
artifact_name: 'SampleFunctionApp'
azure_subscription: 'post-artifact-connection'
function_app_name: 'JavaScriptSampleFunctionApp'

pool:
vmImage: 'ubuntu-16.04'

steps:

- task: DownloadBuildArtifacts@0
inputs:
buildType: 'specific'
project: $(project_name)
pipeline: $(build_pipeline)
# If build Id is not set, get latest build from branch else get specified build id.
${{ if eq(variables['build_id'], '') }}:
buildVersionToDownload: 'latestFromBranch'
${{ if ne(variables['build_id'], '') }}:
buildVersionToDownload: 'specific'
buildId: $(build_id)
branchName: $(branch_name)
downloadType: 'single'
artifactName: $(artifact_name)
downloadPath: '$(System.ArtifactsDirectory)'

- task: AzureFunctionApp@1
inputs:
azureSubscription: $(azure_subscription)
appType: 'functionApp'
appName: $(function_app_name)
package: '$(System.ArtifactsDirectory)/**/*.zip'
deploymentMethod: 'auto'

After this the deployment is done, we can navigate to the function App url and see it in action. Below we can see the HTTPTrigger function in action

HttpTrigger function in action

Other configurations

For more options of the download artifact task you can refer here

For options like deploying to a slots please refer this

For performing slot swapping you can refer the Azure App Service Manage task

To configure App settings please refer the App service settings task

Sharing common code amongst pipelines

From the above example we can see that most of the tasks between the PR build pipeline and Publish Artifact pipeline are common. Similarly if we had 2 function apps instead of 1 we would want to publish artifacts for both function apps without replicating the pipeline code in multiple files. One of the ways to achieve this is using templates.

In this approach we can first move the publish artifact steps into a seperate template yaml file, with function app folder name as a parameter. Consider the file build-publish-artifact-steps-template.yml below

parameters:
- name: 'node_version'
type: string
default: '12.14.1'
- name: 'function_app_name'
type: string
default: 'SampleFunctionApp'
- name: 'code_coverge_threshold'
type: string
default: '60'

steps:

- task: NodeTool@0
inputs:
versionSpec: ${{ parameters.node_version }}

# Install dev dependencies
- script: npm install
workingDirectory: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}
displayName: "Install linting, test and coverage dependencies"

# Perform linting using eslint
- script: npm run-script lint
workingDirectory: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}
displayName: "Run code linting"

# Execute unit tests
- script: npm run-script test
workingDirectory: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}
displayName: "Execute unit tests"
continueOnError: true


# Publish unit test results
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
testRunTitle: 'Jest Unit Tests'
failTaskOnFailedTests: true
displayName: 'Publish unit test report'

- script: npm run-script coverage
workingDirectory: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}
displayName: "Calculate Code Coverage"

# Publish code coverage results
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}/coverage/cobertura-coverage.xml
displayName: "publish code coverage report"

# Fail build if coverage below threshold
- task: BuildQualityChecks@6
inputs:
checkCoverage: true
coverageFailOption: 'fixed'
coverageType: 'lines'
coverageThreshold: ${{ parameters.code_coverge_threshold }}

# Prune dev dependencies
- script: npm prune --production
workingDirectory: $(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}
displayName: "Prune dev dependencies"

- task: ArchiveFiles@2
displayName: "Archive files"
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)/src/${{ parameters.function_app_name }}"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"

- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
ArtifactName: ${{ parameters.function_app_name }}

Next the publish artifact pipeline (azure-pipeline-build-publish-artifact.yml) for both function apps SampleFunctionApp1 and SampleFunctinoApp2 would be as follows:

trigger:
- develop
- master
- release/*


pool:
vmImage: 'ubuntu-latest'

jobs:
- job: SampleFunctionApp1Publish
steps:
- template: build-publish-artifact-steps-template.yml
parameters:
function_app_name: 'SampleFunctionApp1'
- job: SampleFunctionApp2Publish
steps:
- template: build-publish-artifact-steps-template.yml
parameters:
function_app_name: 'SampleFunctionApp2'

With the template any changes in the common steps would just require us to change the common template.

Further work

As a next step I will aim to configure our sample application, with similar quality checks using GitHub Actions instead of Azure Pipelines.

Thanks for reading. Please leave comments here or you can tweet using my twitter handle @manisbindra

--

--

Maninderjit (Mani) Bindra
Microsoft Azure

Gopher, Cloud, Containers, K8s, DevOps | LFCS | CKA | CKS | Principal Software Engineer @ Microsoft