Azure Resource Manager Templates - Working with variables, parameters, functions, and loops

In our introduction to Azure Resource Manager Templates, we wrote a template from scratch. Whenever we used a name for a resource, we had to use the full name. Imagine having to change this value. Sure, you could find and replace all instances, but we all know how this could go wrong.

For a basic introduction, also see our previous article on Azure Resource Manager Templates 101.

Variables

Of course, ARM has mechanisms to deal with this. Under the variables property, you can define key-value pairs. Later, you reference the value by its key. That way, when you want to change the value, you only have to do it once.

"variables": {
  "aspName": "demo-asp1",
  "aspSku": "F1",
  "aspCapacity": "1"
}

To reference the value, use the variables function.

"resources": [
  {
    "name": "[variables('aspName')]",
    "sku": {
      "name": "[variables('aspSku')]",
      "capacity": "[variables('aspCapacity')]"
    }
  }
]

You can also compose variables using other variables and functions.

"variables": {
  "prefix": "demo-",
  "aspName": "[concat(variables('prefix'),'asp1')]",
  "location": "[resourceGroup().location]"
}

Deployment Parameters

Variables centralize the values you have to use throughout your template, but they still live in your deployment files. Imagine you want to deploy the same set of resources for production, staging, and development environments. Ideally, you’d work from one master deployment file.

You can achieve this by using ARM parameters. You can supply the values for these parameters from a file, via the command line, or the user interface.

The parameters property accepts a set of parameter names and the expected type.

"parameters": {
  "name": {
    "type": "string"
  },
  "db": {
    "type": "string",
    "minLength": 3,
    "maxLength": 50,
    "metadata": {
      "description": "Name of the database to create"
    }
  }
}

type is the only required property for every parameter. To provide a description of the parameter, add the metadata property as shown. This is available for any type.

Available types are:

  • string (e.g., “alice”),
  • int (e.g., 42),
  • bool (e.g., true or false),
  • object (e.g., { "firstName": "alice", "lastName": "doe" }),
  • array (e.g., [1,2,3] or ["a","b","c"]).
"parameters": {
  "instances": {
    "type": "int",
    "minValue": 1,
    "maxValue": 10,
    "metadata": {
      "description": "Number of instances to create"
    }
  },
  "names": {
    "type": "array",
    "minLength": 1,
    "maxLength": 10,
    "metadata": {
      "description": "Names of instances"
    }
  },
  "level": {
    "type": "string",
    "allowedValues": ["Small", "Medium", "Large"],
    "defaultValue": "Small"
  }
}

The minLength and maxLength properties are available for string and array. For the latter, these define how many values must be provided at least and at most.

"parameters": {
  "level": {
    "type": "string",
    "allowedValues": ["Small", "Medium", "Large"],
    "defaultValue": "Small"
  }
}

To restrict the values a parameter may have, use the allowedValues property. You can specify a default using the defaultValue property. This works for string and int.

Using parameters is analogous to variables:

"count": "[parameters('level')]"

Deploying with Parameters

You can specify each parameter in the PowerShell command:

New-AzureRmResourceGroupDeployment -Name Demo -ResourceGroupName Demo -TemplateFile demo.json -prefix demoprefix

You can also create a parameters file. The contents are similar to a resource definition file but contain only the schema, contentVersion, and parameters properties.

{
	"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
	"contentVersion": "1.0.0.0",
	"parameters": {
		"prefix": {
			"value": "demoprefix"
		}
	}
}

Then you run the PowerShell command using the TemplateParameterFile parameter:

New-AzureRmResourceGroupDeployment -Name Demo -ResourceGroupName Demo -TemplateFile demo.json -TemplateParameterFile demo.params.json

To load the file from a remote URI instead, use the TemplateParameterUri parameter.

See Microsoft Docs for more information on deploying Azure Resource Manager Templates.

Functions

You can use a number of functions when setting values in templates. Some commonly used ones are:

  • int("1"): converts a string to an integer.
  • add(1,2), mul(1,2), sub(2,1), div(4,2): arithmetic operations.
  • concat("abc","def") or concat([a,b,c],[d,e,f]): concatenates strings or arrays.
  • replace("a.b.c", ".", "-"): replaces parts of strings (result: “a-b-c”).
  • split("a.b.c", "."): splits a string into an array (result: ["a","b","c"]).
  • substring("azure",2,1): returns a specific substring (result: “u”).
  • toLower, toUpper: converts strings to lowercase or uppercase.
  • uniqueString("a","b","c","d"): creates a hash of the input values.

Note: You cannot use functions (or any dynamic expressions) in parameter files.

See Microsoft Docs for more information on Azure Resource Manager Template functions.

Loops

If you want to create a resource multiple times, there are two options: copy-paste the resource definition, or use a parameter to decide how many resources to create.

For this, you can use the special copy property on the resource definition:

"resources": [
  {
    "name": "[concat(variables('aspName'), copyIndex())]",
    "sku": {
      "name": "[variables('aspSku')]",
      "capacity": "[variables('aspCapacity')]"
    },
    "copy": {
      "name": "aspcopy",
      "count": "[parameters('aspCount')]"
    }
  }
]

Note the copyIndex function used as a suffix in the name. This adds a unique number to the resource name, ensuring names remain unique. If you prefer to start with 1, use copyIndex(1).

You cannot use copy on child resources, only on top-level resources.

Another helpful use case is using copy together with an array of names. Assume for example you want to create 3 identical resources but with different names.

"parameters": {
  "country": {
    "type": "array",
    "defaultValue": [
      "Vienna",
      "Berlin",
      "Barcelona"
    ]
  }
},
"resources": [
  {
    "name": "[concat(variables('aspName'), '-', parameters('country')[copyIndex()])]",
    "copy": {
      "name": "countrywebsite",
      "count": "[length(parameters('country'))]"
    }
  }
]

You pass an array of countries in as a parameter. The copy count then uses the length function to determine how many items were passed. Then the suffix for the name is read from that same array using copyIndex.

See Microsoft Docs for more information on loops in Azure Resource Manager Templates.