Frank Fu
Frank Fu

Reputation: 3643

Copy element within copy element in Azure ARM deployment template to create multiple instances of a resource

I'm trying to create do a "nested for loop" in my Azure ARM deployment template via the copy element feature to create multiple instance of the same resource type (Microsoft.Web/sites/hostnameBindings in my case)

More specifically I'm trying to bind multiple hostnames to multiple apps (azure app service websites).

Is this possible? Or would I need to go down the linked templates path?

Here is my attempt so far but I can't get it to work.

parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {    
    "appList": {
      "value": [
        { "appName": "app1", "hostNames": [ "app1.qqq.example.com", "app1.ttt.example.com" ] },
        { "appName": "app2", "hostNames": [ "app2.qqq.example.com" , "app2.ttt.example.com" ] },
        { "appName": "app3", "hostNames": [ "app3.qqq.example.com", "app3.ttt.example.com" ] }    
      ]
    }    
  }
}

template.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {    
    "appList": { "type": "array"  }
  },  
  "resources": [
    {
      "type": "Microsoft.Web/sites/hostnameBindings",
      "name": "parameters('appList')[copyIndex('webAppCopy')]/parameters('appList')[copyIndex('webAppCopy')].hostNames",
      "copy": [
        {
          "name": "webAppCopy",
          "count": "[length(parameters('appList'))]"
        }        
      ],
      "apiVersion": "2016-03-01",
      "location": "[resourceGroup().location]"
    }
  ],
  "outputs": {}
}

Upvotes: 3

Views: 3922

Answers (2)

Frank Fu
Frank Fu

Reputation: 3643

I ended up with a different approach to solve this issue which results in slightly more duplicating names but gives me better flexiblity and readability

parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "webAppAzureNamePrefix": { "value": "mycompanyprefix-" },    
    "appList": { "value": [ "app1", "app2", "app3"]  },
    "hostBindings": {
      "metadata": { "description": "List of host bindings" },
      "value": [
        { "appName": "app1", "hostName": "app1.qqq.example.com" },
        { "appName": "app1", "hostName": "app1.ttt.example.com" },        
        { "appName": "app2", "hostName": "app2.qqq.example.com" },        
        { "appName": "app2", "hostName": "app2.ttt.example.com" },        
        { "appName": "app3", "hostName": "app3.qqq.example.com" },        
        { "appName": "app3", "hostName": "app3.ttt.example.com" },        
      ]
    }
  }
}

template.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "aspName": {
      "type": "string",
      "minLength": 1,
      "metadata": { "description": "Name of App Service Plan" }
    },
    "aspSkuName": {
      "type": "string",
      "allowedValues": [ "F1", "D1", "B1", "B2", "B3", "S1", "S2", "S3", "P1", "P2", "P3", "P4" ],
      "metadata": { "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" }
    },
    "appList": { "type": "array" },
    "hostBindings": { "type": "array" },
    "webAppAzureNamePrefix": { "type": "string" }    
  },
  "resources": [
    {
      "name": "[parameters('aspName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-08-01",
      "sku": { "name": "[parameters('aspSkuName')]" },
      "properties": {
        "name": "[parameters('aspName')]",
        "numberOfWorkers": 1
      }
    },
    {
      "name": "[concat(parameters('webAppAzureNamePrefix'), parameters('appList')[copyIndex()])]",
      "copy": {
        "name": "webAppCopy",
        "count": "[length(parameters('appList'))]"
      },
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-08-01",
      "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', parameters('aspName'))]" ],
      "properties": {
        "name": "[concat(parameters('webAppAzureNamePrefix'), parameters('appList')[copyIndex()])]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('aspName'))]"
      },
      "resources": []
    },    
    {
      "type": "Microsoft.Web/sites/hostnameBindings",
      "name": "[concat(parameters('webAppAzureNamePrefix'),parameters('hostBindings')[copyIndex()].appName, '/',parameters('hostBindings')[copyIndex()].hostName)]",
      "copy": {
        "name": "hostnameCopy",
        "count": "[length(parameters('hostBindings'))]",
        "mode": "Serial",
        "batchSize": 1
      },
      "apiVersion": "2016-03-01",
      "location": "[resourceGroup().location]",
      "properties": {
        "sslState": "SniEnabled",
        "thumbprint": "[reference(resourceId('Microsoft.Web/certificates', parameters('certificateName'))).Thumbprint]"
      },
      "dependsOn": [ "webAppCopy" ]
    }
  ],
  "outputs": {}
}

Upvotes: 1

John Rusk - MSFT
John Rusk - MSFT

Reputation: 643

I've never seen anything in the docs about direct support for nested loops. But you could probably solve this with the numeric functions as follows:

  1. define a template variable that is number-of-apps * number-of-addresses-per-app. Let's call this variable bindingCount
  2. Use bindingCount as the count for your "copy"
  3. When constructing the resource name, use the div and mod functions to take copyIndex and turn it back into an "app index" and a "hostname" index. I think you'll have to do the math inline, in the formula that constructs the name.

Upvotes: 3

Related Questions