Build and release Azure Functions with Azure DevOps

I recently got an Azure Functions version 2.x app up and running in Azure with a build and release pipeline in Azure DevOps and this post is my brain dump of that process.

Create a build pipeline

The first thing I did was to create a new Build pipeline in Azure DevOps. My code was hosted in a repo in DevOps, so I selected the Azure Repos option. I used this template from the Sample ASP.NET Core application for Azure Pipelines docs but did a couple of modifications:

The Microsoft-hosted agent pool provides 6 virtual machine images to choose from, and since my development team is using Visual Studio 2017 I choose the vs2017-win2016 image instead of ubuntu-16.04.

I added code coverage to the test task:

dotnet test --configuration $(buildConfiguration) --logger trx --collect "Code coverage"

The dotnet publish task didn’t produce any files, a problem that someone else had noticed in a GitHub issue:

##[warning]Directory 'D:\a\1\a' is empty. Nothing will be added to build artifact 'drop'.

I resolved it by changing the value of the output parameter from $BUILD_ARTIFACTSTAGINGDIRECTORY to $(Build.ArtifactStagingDirectory):

dotnet publish --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)

The result was a succesfully run build with files published to 'D:\a\1\a', then uploaded to a container '#/0000000/drop'.

Nice! A working build, but I noticed that the task published all of the files instead of a single zip package. I changed that after next step.

Create a release pipeline

Next step was to create a release pipeline. DevOps provides several templates that pre-populate a stage with the appropriate tasks and settings. I chose Deploy a function app to Azure Functions and then some settings needed attention:

Add your artifact from source type Build and then select from the Source dropdown.

For the Deploy Azure App Service task I had to create an Azure service connection for my Azure App Service in Project settings > Pipelines > Service connections.

The task also holds a setting with the file path to the package containing app service contents generated by the build. That settings was defined as:

$(System.DefaultWorkingDirectory)/**/*.zip

And since the files were not zipped as a single package I replaced dotnet publish with the DotNetCoreCLI@2 task in my build pipeline:

- task: DotNetCoreCLI@2
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: False
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: True

Then my artifact was picked up and succesfully deployed to Azure!

The complete azure-pipelines.yml looks like this:

trigger:
- master

pool:
  vmImage: 'vs2017-win2016'

variables:
  buildConfiguration: 'Release'

steps:
- script: |
    dotnet build --configuration $(buildConfiguration)
    dotnet test --configuration $(buildConfiguration) --logger trx --collect "Code coverage"
  failOnStderr: true

- task: DotNetCoreCLI@2
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: False
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: True

- task: PublishTestResults@2
  condition: succeededOrFailed()
  inputs:
    testRunner: VSTest
    testResultsFiles: '**/*.trx'

- task: PublishBuildArtifacts@1