Using Azure DevOps Pipelines to Deploy Azure Functions written in Java

Maninderjit (Mani) Bindra
Microsoft Azure
Published in
8 min readFeb 13, 2019

--

Azure Functions are one of the Azure serverless services which enable you to create a microservices-based application without needing to think about managing the actual infrastructure. Java is one of the languages which you can use to create serverless functions on Azure.

Azure Pipelines are a great way to build, test and deploy application to any cloud.

In this post, we will look at a few of the ways in which you can deploy Java code from a git repository to Azure Functions using Azure Pipelines. We will look at both pipeline as code (azure-pipelines.yaml) and the Azure Pipelines visual designer based approach.

This post assumes that you have already know how to create an Azure Java function, you have pushed the function app’s code to GitHub and that you already have an Azure DevOps / VSTS Project created .

The sample function app and the code repository

The sample function app which will be referred to in the rest of this post is the sample app created when you create a HTTP-triggered Java function in VS Code. The files for this sample app have been added to this github repo along with two additional files: azure-pipelines.yaml and the README.md . An additional folder containing the images used in this blog post has also been added to the repo.

Let us look at portions of some of the key files which will be needed when configuring and testing our pipeline:

The main function code is in the file Function.java file under /src/main. It is a simple function, where if you send the name parameter in query string or body, and the function responds with a simple greeting along with an HTTP 200.

..
.
// Parse query parameter
String query = request.getQueryParameters().get("name");
String name = request.getBody().orElse(query);
if (name == null) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build();
} else {
return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
}
}
}

The next file of the solution we will look at is the pom.xml file at the root of the solution. This file has the functionAppName element which contains the the name of the target folder (highlighted in the code below) where where the deployment package is created by the Maven package command. In the example shown the artifacts are created under target/azure-functions/java-functions-azure-pipelines. When you use VS Code to create the function, this function app name is entered by you. The deployment package created by the Maven package will need to be eventually pushed to the Function Application in Azure.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mani.function.pipelines</groupId>
<artifactId>java-functions-azure-pipelines</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Azure Java Functions</name> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<azure.functions.maven.plugin.version>1.0.0-beta-6</azure.functions.maven.plugin.version>
<azure.functions.java.library.version>1.0.0-beta-5</azure.functions.java.library.version>
<functionAppName>java-functions-azure-pipelines</functionAppName>
<functionAppRegion>westus</functionAppRegion>
<stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
<functionResourceGroup>java-functions-group</functionResourceGroup>
</properties>
.
.
.

Artifacts generated by the Maven Package command

The screen shot below shows the contents of the target directory after the maven package command has been executed:

As per the names in the pom.xml the key artifacts are created in the functionAppName folder name (target/azure-functions/java-functions-azure-pipelines in our example) mentioned in the pom.xml file. The bin folder and the local.settings.json are in the .gitignore file and are not pushed to github.

In order to deploy the function app to Azure the contents of this folder need to pushed to the function app. After successful deployment of these artifacts the kudu console view of the sample function app’s site/wwwroot directory would look something like shown in the screenshot below:

Pushing the maven artifacts to the function app

The recommended way to push the artifacts to the function app would be to zip the function artifacts and then use the Azure App Service Deploy task.

Lets us look at a couple of pipelines using Azure App Service Deploy task. One Visual designer based pipeline, and another yaml-based pipeline.

Visual Designer Based Pipeline

First, we’ll create a new Azure build pipeline, select the source as GitHub, and point it to the git repository, and branch:

Next select the Maven template, and hit apply

The maven template adds three tasks: the “Maven” task, the “Copy Files” task and the “Publish Artifact” task. You can select the agent where this task is executed, in this sample the Hosted VS 2017 agent has been selected. Next we configure the three tasks:

Now we configure the Maven task. Point it to the pom.xml:

Now configure the Copy Files task. The configuration below results in all files and folders under the target/azure-functions directory being copied to the artifact staging directory:

Now we need to zip the copied artifacts. To do this let‘s’ add the Archive task:

Now we configure the Archive task:

The main setting here is configuring the folder to archive. As we have had seen earlier this value will depend on the functionAppName configured in your maven pom file. In this case the value configured is “$(build.artifactstagingdirectory)/target/azure-functions/$(functionAppMvnTargetFolder)”, where the value of the build variable functionAppMvnTargetFolder is the same as the value of functionAppName in the pom file. After this we can uncheck the prepend root folder name to the archive paths checkbox. We now choose the Archive file to create, in this case we select the default value of “$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip”.

Now that we have the archived zip file, we can use the App Service Deploy task to push the latest function to Azure. Typically to push the artifacts to Azure we would create a new release linked to this build. Here we are pushing the artifacts from the build itself. Let us first add the task:

And now we can configure the task:

We select version 4 of this task, select the azure subscription, select the App service type as Function App and select the App Service Name for our function. Next we provide the folder which this task deploys to the function App, in our case this is “$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip”.

After this we select the deployment method. We have few options here, we could select “Web deploy” or “Zip Deploy”, but the preferred option for the reasons mentioned here is “Run From Package”.

Next we can optionally configure the Publish Artifact task, to publish all copied artifacts. Please note that the publish artifacts task is needed if you have a separate release pipeline, for this post, for the sake of simplicity we are deploying in the build pipeline itself. For completeness, let us see how to configure the publish artifact task:

The main value here is the path to publish, which we point to the zip file created earlier.

Now that we have the tasks configured, let us configure the values of the build variables. Instead of hard coding these values into the pipeline, adding pipeline variables in the task makes pipeline more configurable:

Thats it the visual designer pipeline is configured which is ready to continuously deploy code to the Azure function. Let us now look at a yaml file based deployment pipeline which does the same tasks as the pipeline designed above.

Pipeline as code

The first step of creating the pipeline is the same as the earlier pipeline where we configure the git repository. In the next step, we’ll select the yaml-based template:

Next we select the pipeline as code yaml file, which in the case of our example is the azure-pipelines.yaml:

Let us have a look at the pipeline yaml file:

resources:
- repo: self
queue:
name: Hosted VS2017
demands: maven
# Maven Package - create the function artifacts in
steps:
- task: Maven@2
displayName: 'Maven pom.xml'
inputs:
mavenPomFile: '$(mavenPOMFile)'
# Copy files
- task: CopyFiles@2
displayName: 'Copy Files to: $(build.artifactstagingdirectory)'
inputs:
SourceFolder: '$(system.defaultworkingdirectory)'
Contents: '**/azure-functions/**'
TargetFolder: '$(build.artifactstagingdirectory)'
# Create Archive : zip file
- task: ArchiveFiles@2
displayName: 'Archive $(build.artifactstagingdirectory)/target/azure-functions/$(functionAppMvnTargetFolder)'
inputs:
rootFolderOrFile: '$(build.artifactstagingdirectory)/target/azure-functions/$(functionAppMvnTargetFolder)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
replaceExistingArchive: true
# Deploy using Azure App Service Deploy - Webdeployment
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy: javapipeline'
inputs:
azureSubscription: '$(azureSubcriptionKey)'
appType: functionApp
WebAppName: yourAppServiceName
packageForLinux: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
enableCustomDeployment: true
DeploymentType: runFromZip
TakeAppOfflineFlag: false
ExcludeFilesFromAppDataFlag: false
RenameFilesFlag: false
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'

These are the same tasks as the visual pipeline, and we need to set some build variables. We need to set values for the variables mavenPOMFile, functionAppMvnTargetFolder and azureSubcriptionKey. The azureSubscriptionKey needs to be set to the name of the Azure DevOps Service connection for the Azure subscription. Let us look at the variable values:

The configuration of the yaml pipeline is complete as well. Now let us test the pipelines. Easiest way to test the continuous deployment is by changing the function app Java file and merging the changes into the repository.

If while executing the yaml pipeline you come across an authorization error follow the workaround mentioned in troubleshoot authorization for a yaml pipeline.

Conclusion

It must be noted that the ideal way to deploy functions is using the App Service Deploy task which this post describes. However If for some reason/constraint you need to use ftp to push the function to Azure you can look at the following post.

--

--

Maninderjit (Mani) Bindra
Microsoft Azure

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