Simplify your deployment with nested Azure Resource Manager (ARM) templates
Tags: devops, azure, arm, opensource
Published: 2020-05-06 12:43:34

---
title: Simplify your deployment with nested Azure Resource Manager (ARM) templates
published: true
date: 2020-05-06 10:38:00 UTC
tags: devops,azure,arm,opensource
canonical_url: http://www.frankysnotes.com/2020/05/simplify-your-deployment-with-nested.html
---

Most solutions, if not all, are composed of multiple parts: backend, frontend, services, APIs, etc. Because all parts could have a different life-cycle it's important to be able to deploy them individually. However, sometimes we would like to deploy everything at once.  It's exactly the scenario I had in a project I'm working on where with backend and one frontend. 

In this post, I will explain how I use [nested Azure Resource Manager (ARM) templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates?WT.mc_id=devto-blog-frbouche) and conditions to let the user decide if he wants to deploy only the backend or the backend with a frontend of his choice. All the code will be available in [GitHub](https://github.com/FBoucher/AzUrlShortener) and if you prefer, a video version is available below.
*(This post is also available in [French](http://www.cloudenfrancais.com/posts/Simplifier-vos-deploiements-en-utilisant-des-modeles-ARM-imbriques.html))*


### The Context
  
The project used in this post my open-source budget-friendly Azure URL Shortener. Like mentioned previously the project is composed of two parts. The backend leverage Microsoft serverless Azure Functions, it a perfect match in this case because it will only run when someone clicks a link. The second part is a frontend, and it's totally optional. Because the Azure Functions are HTTP triggers they act as an API, therefore, they can be called from anything able to do an HTTP call. Both are very easily deployable using an ARM template by a PowerShell or CLI command or by a one-click button directly from GitHub. 
  

### The Goal
  
At the end of this post, we will be able from one-click to deploy just the Azure Functions or to deploy them with a frontend of our choice (I only have one right now, but more will come). To do this, we will modify the "backend" ARM template using condition and nest the ARM template responsible for the frontend deployment. 

The ARM templates are available here in there [initial](https://github.com/FBoucher/AzUrlShortener/tree/master/tutorials/optional-arm/before) and [final](https://github.com/FBoucher/AzUrlShortener/tree/master/tutorials/optional-arm/before/after) versions.
  

### Adding New Inputs
  
We will nest the ARM templates, this means that our backend template (azureDeploy.json) will call the frontend template (adminBlazorWebsite-deployAzure.json). Therefore we need to add all the required information to `azureDeploy.json` to make sure it's able to deploy `adminBlazorWebsite-deployAzure.json` successfully. Looking at the parameter required for the second template, we only two need values `AdminEMail` and `AdminPassword`. All the other can be generated or we already have them.

We will need also another parameter the will act as our selection option. So let's add a parameter named `frontend` and allowed only two values: **none** and **adminBlazorWebsite**. If the value is **none** we only deploy the Azure Function. When the value is **adminBlazorWebsite** we will deploy the Azure Function, of course, but we will also deploy an admin website to go with it.

Following the best practices, we add clear detail and add those three parameters in the **parameters** section of the ARM template.
  

```json
"frontend": {
    "type": "string",
    "allowedValues": [
        "none",
        "adminBlazorWebsite"
    ],
    "defaultValue": "adminBlazorWebsite",
    "metadata": {
        "description": "Select the frontend that will be deploy. Select 'none', if you don't want any. Frontend available: adminBlazorWebsite, none. "
    }
},
"frontend-AdminEMail": {
    "type": "string",
    "defaultValue": "",
    "metadata": {
        "description": "(Required only if frontend = adminBlazorWebsite) The EMail use to connect into the admin Blazor Website."
    }
},
"frontend-AdminPassword": {
    "type": "securestring",
    "defaultValue": "",
    "metadata": {
        "description": "(Required only if frontend = adminBlazorWebsite) Password use to connect into the admin Blazor Website."
    }
}
```
  

### Nested Templates
  
Let's assume for now that we always deploy the website when we deploy the Azure Function, to keep things simple. What we need now is to used nested ARM template, and that when you deploy an ARM template from inside another ARM template. This is done with a `Microsoft.Resources/deployments` node. Let's look at the code:

```json
{
    "name": "FrontendDeployment",
    "type": "Microsoft.Resources/deployments",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', variables('funcAppName'))]",
        "[resourceId('Microsoft.Web/sites/sourcecontrols', variables('funcAppName'), 'web')]"
    ],
    "resourceGroup": "[resourceGroup().name]",
    "apiVersion": "2019-10-01",
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri": "[variables('frontendInfo')[parameters('frontend')].armTemplateUrl]"
        },
        "parameters": {
            "basename": {
                "value" : "[concat('adm', parameters('baseName'))]"
            },
            "AdminEMail": {
                "value" : "[parameters('frontend-AdminEMail')]"
            },
            "AdminPassword": {
                "value" : "[parameters('frontend-AdminPassword')]"
            },
            "AzureFunctionUrlListUrl": {
                "value" : "[concat('https://', reference(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '2018-02-01').hostNames[0], '/api/UrlList?code=', listkeys(concat(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '/host/default/'),'2016-08-01').functionKeys.default)]"
            },
            "AzureFunctionUrlShortenerUrl": {
                "value" : "[concat('https://', reference(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '2018-02-01').hostNames[0], '/api/UrlShortener?code=', listkeys(concat(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '/host/default/'),'2016-08-01').functionKeys.default)]"
            },
            "GitHubURL": {
                "value" : "[parameters('GitHubURL')]"
            },
            "GitHubBranch": {
                "value" : "[parameters('GitHubBranch')]"
            },
            "ExpireOn": {
                "value" : "[parameters('ExpireOn')]"
            },
            "OwnerName": {
                "value" : "[parameters('OwnerName')]"
            }

        }
    }
}
```
  

If we examine this node, we have the classic: name, type, dependsOn, resourceGroup, apiVersion. Here We really want the Azure Functions to be fully deployed so we need the FunctionApp to be created AND the GitHub sync to be complete, this is why there is also a dependency on `Microsoft.Web/sites/sourcecontrols`.

In **properties** we will pass the mode as **Incremental** as it will leave unchanged resources that exist in the resource group but aren't specified in the template.
  
> Learn more about the [Azure Resource Manager deployment modes here](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-modes?WT.mc_id=devto-blog-frbouche) as they are very powerful.
  
The second property is **templateLink**. This is really important as it's the URL to the other ARM template. That URI must not be a local file or a file that is only available on your local network. You must provide a URI value that downloadable as HTTP or HTTPS.  In this case, it's a variable that contains the GitHub URL where the template is available.

Finally, we have the **parameters**, and this is how we pass the values to the second template. Let's skip those where I just pass the parameter value from the caller to the called, and focus on **basename**, **AzureFunctionUrlListUrl**, and **AzureFunctionUrlShortenerUrl**.

For **basename** I just add a prefix to the parameter **basename** received, this way the resource names will be different but we can still see the "connection". That's purely optional, you could have added this value in a parameter to `azureDeploy.json`, I prefer keeping the parameters a minimum as possible as I think it simplifies the deployment for the users.

Finally for **AzureFunctionUrlListUrl**, and **AzureFunctionUrlShortenerUrl** I needed to retrieve the URL of the Azure Function with the security token because they are secured. I do that by concatenating different parts.
  

| Component  | Value 
-------------|-------
| Beginning of the URL | 'https://'
| Reference the Function App, return the value of hostname | reference(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '2018-02-01').hostNames[0]
| Specify the Function targeted in this case UrlList. And starting the querystring to pass the code (aka. security token) | '/api/UrlList?code='
| Using the new `listkeys` function to retrieve the default Function key. | listkeys(concat(resourceId('Microsoft.Web/sites/', variables('funcAppName')), '/host/default/'),'2016-08-01').functionKeys.default
  

### Conditional parts
  
Now that the second ARM template can be deployed, let's add a condition so it gets, indeed, deploy only when we desire. To do this it's very simple, we need to add a property `condition`.
  
```json
{
    "name": "FrontendDeployment",
    "type": "Microsoft.Resources/deployments",
    "condition": "[not(equals(parameters('frontend'), 'none'))]",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', variables('funcAppName'))]",
        "[resourceId('Microsoft.Web/sites/sourcecontrols', variables('funcAppName'), 'web')]"
    ]
}
```
  
In this case, is the value of the parameter is different then **none**, the nested template will be deployed. When a condition end-up being "false", the entire resource will be ignored during the deployment. How simple or complex are your conditions... that's your choice!

Happy deployment. :)


{% youtube IePDTQk6Bz8 %}