Step by Step ARM Templates - Iterating and creating multiple instances of a resource

@20aman    Sep 27, 2016

Index of all blogs in this Step by Step ARM Templates series is located here: Step by Step Azure Resource Manager (ARM) Templates - Index

In Azure Resource Manager (ARM) templates, you can define the variable once and then iterate/loop over that definition and create multiple instances of that resource. There are 3 special constructs in ARM templates to help you with this.

These constructs are:

  • copy - This is a property that is defined within the resource. This is the construct which when defined indicates that this resource needs to be looped over and created multiple times. It also specifies the number of times to iterate via "count" property.
  • copyIndex() - Used to access the current iteration value. Its value for the first iteration is zero. For the second iteration, its value is 1 and so on... You can pass it an integer (number) as a parameter. Whatever number you pass that will become the value for the first iteration and subsequent iterations. E.g. copyIndex(20) will compute to 20 in the first iteration, 21 in the second iteration and so on.
  • length - This is the method of arrays. It computes the number of elements in an array. It can be used to set the "count" property of "copy" construct.

Note: Arrays are always zero indexed. What that means is that the first element of the array is indexed at 0, the second element of the array is indexed at 1, and so on...

1. Simple Example

Let us understand these constructs using an example.

"parameters": { 
  "count": { 
    "type": "int", 
    "defaultValue": 3 
  } 
}, 
"resources": [ 
  { 
      "name": "[concat('HarvestingClouds-', copyIndex(100))]", 
      "type": "Microsoft.Web/sites", 
      "location": "Central US", 
      "apiVersion": "2015-08-01",
      "copy": { 
         "name": "websitescopy", 
         "count": "[parameters('count')]" 
      }, 
      "properties": {
          "serverFarmId": "hostingPlanName"
      }
  } 
]

The above example will result in creation of below 3 web apps in Azure:

  • HarvestingClouds-100
  • HarvestingClouds-101
  • HarvestingClouds-102

Note the usage of "copy" property in the above code example:

 "copy": { 
             "name": "websitescopy", 
             "count": "[parameters('count')]" 
          }

As you can notice above, the value of this property is another JSON object. This object has further two properties:

  • First is the name property, which provides the name to the looping construct. This can be any meaningful name.
  • The second property is the count, which specifies how many times this resource definition should be deployed. Note that the value is set to the parameter named "count". The name of the parameter can be anything but the value of the parameter has to be a number (i.e. an integer).

Next, note how the name of the web application is constructed using the copyIndex() helper function.

"name": "[concat('HarvestingClouds-', copyIndex(100))]"

The above value uses two helper functions. First is the "concat()" which is concatenating (i.e. joining) two values. First value is the prefix string "HarvestingClouds-". Second parameter and the second helper function is copyIndex(100). This specifies the current iteration value, which is offset with 100. So for the first iteration, the value will be 0+100 = 100, for the second iteration the value will be 1+100 = 101 and so on...

2. Example with an Array

Let's assume that you want to deploy multiple web apps for different purposes. You need one web app for Production, one for Staging or testing and one for Development. You want to name the web apps deployed with the purpose concatenated. The below example uses an array to set the values for the web app name:

"parameters": { 
  "purpose": { 
     "type": "array", 
         "defaultValue": [ 
         "Production", 
         "Staging", 
         "Development" 
      ] 
  }
}, 
"resources": [ 
  { 
      "name": "[concat('HarvestingClouds-', parameters('purpose')[copyIndex()])]", 
      "type": "Microsoft.Web/sites", 
      "location": "Central US", 
      "apiVersion": "2015-08-01",
      "copy": { 
         "name": "websitescopy", 
         "count": "[length(parameters('purpose'))]" 
      }, 
      "properties": {
          "serverFarmId": "hostingPlanName"
      } 
  } 
]

The output of the above sample will be 3 web apps deployed in Azure with following names:

  • HarvestingClouds-Production
  • HarvestingClouds-Staging
  • HarvestingClouds-Development

Note in the above code sample that the parameter "purpose" is an array with 3 values i.e. Production, Staging, and Development. Then in the "copy" construct the count property is set using the length of this array as shown below. As there are 3 elements in the array, the value of count will be 3 and the resource will be deployed 3 times.

"count": "[length(parameters('purpose'))]" 

Next, the name of the web app is set using the copyIndex() and the array itself as shown below:

"name": "[concat('HarvestingClouds-', parameters('purpose')[copyIndex()])]"

As earlier, it uses concat helper function to add two strings. The first string is simple text i.e. "HarvestingClouds-", which becomes the prefix for the web app name. Second is finding out the value of the array based on the current iteration. For the first iteration, copyIndex() will compute to zero, therefore the second parameter becomes parameters('purpose')[0]. This will fetch the 0th element of the array which is Production. Similarly, for the second iteration, copyIndex() will compute to 1, therefore the second parameter becomes parameters('purpose')[1]. This will fetch the second element of the array (or element at index value 1) which is Staging, and so on...

3. Depending upon resources being deployed by the copy Loop

Let's assume you want to deploy a storage account. But you want to deploy it only after all the web apps are deployed by the loop. In this scenario, the dependsOn property of a resource is set to the name of the "copy" property of the resource, rather than the resource itself.

    {
        "apiVersion": "2015-06-15",
        "type": "Microsoft.Storage/storageAccounts",
        "name": "teststorage101",
        "location": "[resourceGroup().location]",
        "properties": {
            "accountType": "Standard_LRS"
         }
       "dependsOn": ["websitescopy"]
    }

Note above that the dependsOn property is set to the name property of the copy in the earlier web app example. This storage account will not be deployed until all 3 web apps are not deployed.

4. Limitations

There are two limitations on the use of the copy to iterate and create multiple resource instances:

  1. Nested Resources - You cannot use a copy loop for a nested resource. If you need to create multiple instances of a resource that you typically define as nested within another resource, you must instead create the resource as a top-level resource and define the relationship with the parent resource through the type and name properties.
  2. Looping Properties of a Resource - You can only use copy on resource types, not on properties within a resource type. E.g. Creating multiple data disks within a VM.

That is all there is to iterate and creating multiple resources from a single definition. When your templates will start becoming complex then these constructs/helper functions will help you a lot. E.g. you may need to deploy multiple load balanced resources, then you can use the concepts defined in this post.

You can also refer the official documentation here: copy, copyIndex, and length





Comments powered by Disqus