Automatically create and deploy Java Azure Functions sample app to a Function App.
Tags: azure, serverless, java
Published: 2020-08-13 07:45:44

The main tooling for the Azure Functions with Java is `maven`. I wanted to automatically create an sample app and deploy to the Premium Linux App. How can we do it? 

# Create a sample project

You can follow the quick start. However, I wanted to automate it.

* [Quickstart: Create a function in Azure that responds to HTTP requests](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function-azure-cli?tabs=bash%2Cbrowser&pivots=programming-language-java)

To automate this part is quite easy. If you try the tutorial, you need to manually input some parameters. `groupId`, `artifactId`, `version` and `package` is the one. Using `-D` flag, we can pass it.

```bash
mvn archetype:generate \
  -DarchetypeGroupId=com.microsoft.azure \
  -DarchetypeArtifactId=azure-functions-archetype \
  -DinteractiveMode=false \
  -DgroupId=com.fabrikam \
  -DartifactId=fabrikam-functions \
  -Dversion=1.0-SNAPSHOT \
  -Dpackage=com.fabrikam
```

It will create an `pom.xml` file and sample project. It is ready to deploy. 

# The problem

However, the problem is, the generated `pom.xml` is like below.

## Java version on properties

Obviously, it is fixed to the `Java8`. 

```xml
        <java.version>1.8</java.version>
``` 

## Azure Functions Maven Plugin Config for Function APp.

`azure-functions-maven-plugin` can create/deploy an app to  `function app`, however, it is already configured. If we want to automate it, we should modify the `pom.xml`. It is windows, however, I want to use Linux. I want to modify a lot of parts.I can write some app to modify this `pom.xml`, however, it is obviously, not cool. How can I modify it? 

```xml
            <plugin>
                <groupId>com.microsoft.azure</groupId>
                <artifactId>azure-functions-maven-plugin</artifactId>
                <version>${azure.functions.maven.plugin.version}</version>
                <configuration>
                    <!-- function app name -->
                    <appName>${functionAppName}</appName>
                    <!-- function app resource group -->
                    <resourceGroup>java-functions-group</resourceGroup>
                    <!-- function app service plan name -->
                    <appServicePlanName>java-functions-app-service-plan</appServicePlanName>
                    <!-- function app region-->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-regions for all valid values -->
                    <region>westus</region>
                    <!-- function pricingTier, default to be consumption if not specified -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-pricing-tiers for all valid values -->
                    <!-- <pricingTier></pricingTier> -->
                    
                    <!-- Whether to disable application insights, default is false -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details for all valid configurations for application insights-->
                    <!-- <disableAppInsights></disableAppInsights> -->
                    <runtime>
                        <!-- runtime os, could be windows, linux or docker-->
                        <os>windows</os>
                        <javaVersion>8</javaVersion>
                        <!-- for docker function, please set the following parameters -->
                        <!-- <image>[hub-user/]repo-name[:tag]</image> -->
                        <!-- <serverId></serverId> -->
                        <!-- <registryUrl></registryUrl>  -->
                    </runtime>
                    <appSettings>
                        <property>
                            <name>FUNCTIONS_EXTENSION_VERSION</name>
                            <value>~3</value>
                        </property>
                    </appSettings>
                </configuration>
```

## Generated pom.xml

```xml
<?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.fabrikam</groupId>
    <artifactId>fabrikam-functions</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Azure Java Functions</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <azure.functions.maven.plugin.version>1.6.0</azure.functions.maven.plugin.version>
        <azure.functions.java.library.version>1.3.1</azure.functions.java.library.version>
        <functionAppName>fabrikam-functions-20200812194325715</functionAppName>
        <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.microsoft.azure.functions</groupId>
            <artifactId>azure-functions-java-library</artifactId>
            <version>${azure.functions.java.library.version}</version>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.4.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.23.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.microsoft.azure</groupId>
                <artifactId>azure-functions-maven-plugin</artifactId>
                <version>${azure.functions.maven.plugin.version}</version>
                <configuration>
                    <!-- function app name -->
                    <appName>${functionAppName}</appName>
                    <!-- function app resource group -->
                    <resourceGroup>java-functions-group</resourceGroup>
                    <!-- function app service plan name -->
                    <appServicePlanName>java-functions-app-service-plan</appServicePlanName>
                    <!-- function app region-->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-regions for all valid values -->
                    <region>westus</region>
                    <!-- function pricingTier, default to be consumption if not specified -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-pricing-tiers for all valid values -->
                    <!-- <pricingTier></pricingTier> -->
                    
                    <!-- Whether to disable application insights, default is false -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details for all valid configurations for application insights-->
                    <!-- <disableAppInsights></disableAppInsights> -->
                    <runtime>
                        <!-- runtime os, could be windows, linux or docker-->
                        <os>windows</os>
                        <javaVersion>8</javaVersion>
                        <!-- for docker function, please set the following parameters -->
                        <!-- <image>[hub-user/]repo-name[:tag]</image> -->
                        <!-- <serverId></serverId> -->
                        <!-- <registryUrl></registryUrl>  -->
                    </runtime>
                    <appSettings>
                        <property>
                            <name>FUNCTIONS_EXTENSION_VERSION</name>
                            <value>~3</value>
                        </property>
                    </appSettings>
                </configuration>
                <executions>
                    <execution>
                        <id>package-functions</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <overwrite>true</overwrite>
                            <outputDirectory>${stagingDirectory}</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${project.basedir}</directory>
                                    <includes>
                                        <include>host.json</include>
                                        <include>local.settings.json</include>
                                    </includes>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${stagingDirectory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                            <includeScope>runtime</includeScope>
                            <excludeArtifactIds>azure-functions-java-library</excludeArtifactIds>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!--Remove obj folder generated by .NET SDK in maven clean-->
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>obj</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

``` 

# How to create a Zip file to deploy Azure?

I thought that, it is not impossible, however, difficult to modify the `pom.xml` it's self, can we modify it with command line? 

As I tested, we can modify `properties` part, however, not for `azure functions maven plugin` configuration. I'm asking how to modify it. Once I've got an answer, I'll update this blog. (8/13/2020)

If we can create a Zip file to deploy to Azure, and forget about deployment using `maven`, it might work and much easy to modify the deployment of Function App. We can use ARM template and Terraform. 

* [Deploy Premium Linux Function App with ARM template](https://dev.to/tsuyoshiushio/deploy-premium-linux-function-app-with-arm-template-2i6n)
* [Deploy Linux AppService Function App with ARM template](https://dev.to/tsuyoshiushio/deploy-linux-appservice-with-arm-template-3556)

I prefer the approach of [Each Program Does One Thing Well](https://opensource.com/business/15/2/how-linux-philosophy-affects-you). I remove the responsibility of deployment from `maven`. It must work. 

## clean and package with modifying `properties`
To modify the properties are easy. just use `-D` flag with the property that you want to modify. It works. 

```bash
cd fabrikam-functions
mvn clean package -DfunctionAppName=sample
```
You will see that structure is created. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7o3c5ie6t7dg5mx0n0t5.png)

Unfortunately, the maven plugin doesn't have a goal that create zip file. They have deploy goal, however, we can't modify `pom`. 

## Create a zip file only
Just archive the red line part. `zip` command might need to install. I'm using `WSL2`. I needed to install the command. 
`-r` means recursive, `-q` means quiet. 

```bash
# packaging as a zip
cd target/azure-functions/sample
zip -r -q sample . 
```

## Deploy to the azure
Instead of using `maven` I use Azure CLI command instead. I deploy the zip file with [Zip deployment](https://docs.microsoft.com/en-us/azure/azure-functions/deployment-zip-push#cli). It works both for Linux AppService Function App and Linux Premium!

```bash
# deploy the app to the function app
az functionapp deployment source config-zip -g ${resourceGroup} -n ${baseName}-app --src sample.zip 
```
**NOTE:** The official document says 
> When deploying your functions to a Linux function app in a Premium plan, you should always run from the package file and publish your app using the Azure Functions Core Tools

However, it is old information. The `az` command is already updated and ready to go!

# Conclusion
By the series of the experiment, I successfully automate my workflow that is deploy function app with complex configuration and deploy sample app. Next time, I'll use [terratest](https://github.com/gruntwork-io/terratest) to automate the endpoint testing. 

TBH, I'm not an expert of `maven`. If you have an better way to do the same thing, could you leave your comment, please?

The sample repo 
* [FunctionsDeploymentTestingSuite](https://github.com/TsuyoshiUshio/FunctionsDeploymentTestingSuite)

# Resources

* [azure-maven-plugins](https://github.com/microsoft/azure-maven-plugins)