Azure Resource Manager (ARM) templates are a great solution for creating a repeatable environment. Using Infrastructure as Code (IaC) makes  it easy to manage configurations, avoid dependency issues, and prevent server drift. Using IaC also means that you can recreate the environment as often as necessary to ensure it remains consistent. This gets complicated when parts of the environment cannot be re-deployed without side-effects. This article will examine a solution to this situation using condition to customize the resource deployments. It will also look at using logical functions to select between two different templates.

The Azure Key Vault Problem

As an example, let’s look at Azure Key Vault. When an ARM template is deployed, its resources are re-applied.. For keys in a Key Vault, this means a new version of the key is created. This occurs even if nothing has changed. This may be undesirable from a maintenance perspective.

There’s also a more subtle bug in Key Vault. This happens when the administrative permissions are not included in the ARM template. Administrators assigned in the portal appear to have appropriate permissions when viewed online. Whenever those users attempt to view or edit a key, however, they get a permission error. This is because the permissions were removed during the redeployment and are incorrectly presented by portal. To correct this situation, you must remove and re-assign the user’s permissions. Alternatively, you can include the user’s GUID identifier in the ARM template. Neither of these is often a desirable answer.

A Conditional Solution

This brings us to the concept of the first-run template. For this approach, you create a parameter in the ARM template to indicate this is a first run. This value should default to false. This ensures it will only be true when a user explicitly overrides it. Next, use a condition in the resources that need to be skipped. When a condition evaluates to false, the resource is not deployed. During an initial deployment, the value should be explicitly set to true. After that, the value should be allowed to default to false to ensure the resource is no longer updated.

It’s worth mentioning that this method works when you are doing Incremental Mode deployments. If you’re not familiar with the concept, the “mode” determines how to handle deleted resources. Both modes will create resources that don’t exist and update resources that already exist,. Incremental Mode ignore resources that are not part of the ARM template. By comparison, Complete Mode will delete resources that are not included in the ARM template definition.

Let’s see how this might be organized. In a typical ARM template, you would have multiple resources. To keep this example simple, I’ve just included the single Key Vault resource.

{
   "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.0",
   "parameters": { 
     "isFirstRun": { 
       "type": "boolean",
       "defaultValue": "false"
     }
   },
   "resources": [
   {
     "type": "Microsoft.KeyVault/vaults",
     "apiVersion": "2016-10-01",
     "location": "[resourceGroup().location]",
     "name":"mykeyvault",
     "condition": "[parameters('isFirstRun')]",
     "properties": {
       "enabledForDeployment": "false",
       "enabledForTemplateDeployment": "false",
       "enabledForVolumeEncryption": "false",
       "tenantId": "[subscription().tenantId]",
       "accessPolicies": [],
       "sku": {
         "name": Standard",
         "family": "A"
       }
     }
   }]
 }

Notice the condition for the Key Vault resource. The value for this condition is obtained from a parameter provided by the user called isFirstRun. If that value is not provided, defaultValue ensures that isFirstRun is false. Consequently, unless the user explicitly sets the value to true the resource will never be deployed. By setting this value to true when the template is first deployed, we ensure that the Key Vault will be created.

A Logical Solution

Let’s look at a more advanced situation. In this case, we will apply a different virtual machine resource configuration depending on whether this is a new deployment or an existing deployment. As a best practice, we’ll keep each virtual machine definition in its own ARM template file. It will be referenced as a nested template during deployment. This allow us complete control over the content of the resource definition. This makes customization easier and makes the templates reusable. On the first run, we want to use the template virtual-machine-new.json. For later runs, we want the configuration loaded from virtual-machine-existing.json.

Let’s see how this template would look.

{
   "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.0",
   "parameters": { 
     "isFirstRun": { 
       "type": "boolean",
       "defaultValue": "false"
     }
   },
   "variables": {
     "templatename": "[if(parameters['isFirstRun'], 'new', 'existing')]",
     "templatelink": "[concat('https://my-template-source.com/Sample/Templates/master/virtual-machine-', variable('templatename'), '.json')]"
   },
   "resources": [
     {
       "apiVersion": "2015-01-01",
       "name": "vmTemplate",
       "type": "Microsoft.Resources/deployments",
       "properties": {
         "mode": "incremental",
         "templateLink": {
           "uri": "[variables('templatelink')]",
           "contentVersion": "1.0.0.0"
         },
       "parameters": {
       }
     }]
 }

This code is similar to our last example. The isFirstRun parameter is used to define the value of the variable templatename. The if logical function sets a value based on our conditional parameter. The first time the template is deployed, the value of templatename is set to new; otherwise, it is set to existing. The concat function concatenates templatename to a base URL. This URL contains the path to the nested templates. The value is assigned to the variable templatelink and passed to the the deployment resource’s templateLink uri parameter. As a result, the correct template is used by the deployment.

Summary

As you can see, it’s very easy to take advantage of condition and logical functions (like if) to customize the Azure Resource Manager templates to work for first-run and complex deployments. As a practical example, you could customize the VM based on the operating system used for the base image. Hopefully, this will allow you to bring some flexibility (and creativity!) to your templates.

Happy DevOp’ing!