3
votes

I have a problem I can't find a solution for, it's when I try to use copy to create multiple parts dynamically in my dashboard ARM template.

I get the error when I try to use "[copyIndex()]" in one of the keys, as I understand from the error message this will not work. But I'm not sure how I can get around the issue, so any ideas would be appreciated.

"copy": [
  {
    "name": "servers",
    "count": "[length(parameters('locationKeys'))]",
    "input": {
      "[copyIndex('servers')]": {
        "position": {
          "x": "[mul(copyIndex('servers'), 4)]",
          "y": 1,
          "colSpan": 2,
          "rowSpan": 1
        }
      }
    }
  }
]

As you can see in the example above this is what fails

"[copyIndex('servers')]": {

And I get this error

Error: Code=InvalidTemplate; Message=Deployment template validation failed: 
'Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.'.

The structure to create the dashboard looks like this

"properties": {
  "lenses": {
    "0": {
      "order": 0,
      "parts": {
        "0": {},
        "1": {},
        ...

And I have the copy function under the "parts" key.

The way around this would be to remove the copy function and duplicate the code, but then I have a hard time to get it to work with any given number of "locations" since I need to hard code them.

Basically this is what I want to get in the end.

enter image description here

Are there any other good solutions to get around this problem?

UPDATE

These are the two templates I reference from my Azure DevOps task in the pipeline. Note that I now need to do parameters('locationKeys')[0] and duplicate all the blocks instead of doing a copy with parameters('locationKeys')[copyIndex()].

parameters.json

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

deploy.json

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "appName": {
      "type": "string",
      "defaultValue" : "myApp"
    },
    "locationNames": {
      "type": "array"
    }
  },
  "variables": {
    "dashboardName": "[concat(parameters('appName'), '-dashboard')]"
  },
  "resources": [
    {
      "name": "[variables('dashboardName')]",
      "type": "Microsoft.Portal/dashboards",
      "apiVersion": "2015-08-01-preview",
      "location": "westeurope",
      "tags": {
        "hidden-title": "24/7 Operations"
      },
      "properties": {
        "lenses": {
          "0": {
            "order": 0,
            "parts": {
              "0": {
                "position": {
                  "x": 0,
                  "y": 0,
                  "colSpan": 4,
                  "rowSpan": 1
                },
                "metadata": {
                  "inputs": [],
                  "type": "Extension/HubsExtension/PartType/MarkdownPart",
                  "settings": {
                    "content": {
                      "settings": {
                        "content": "[concat('# ', parameters('locationNames')[0])]",
                        "title": "",
                        "subtitle": ""
                      }
                    }
                  }
                }
              },
              "1": {
                "position": {
                  "x": 4,
                  "y": 0,
                  "colSpan": 4,
                  "rowSpan": 1
                },
                "metadata": {
                  "inputs": [],
                  "type": "Extension/HubsExtension/PartType/MarkdownPart",
                  "settings": {
                    "content": {
                      "settings": {
                        "content": "[concat('# ', parameters('locationNames')[1])]",
                        "title": "",
                        "subtitle": ""
                      }
                    }
                  }
                }
              },
              "2": {
                "position": {
                  "x": 8,
                  "y": 0,
                  "colSpan": 4,
                  "rowSpan": 1
                },
                "metadata": {
                  "inputs": [],
                  "type": "Extension/HubsExtension/PartType/MarkdownPart",
                  "settings": {
                    "content": {
                      "settings": {
                        "content": "[concat('# ', parameters('locationNames')[2])]",
                        "title": "",
                        "subtitle": ""
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "dependsOn": []
    }
  ],
  "outputs": {
    "resourceGroupId": {
      "type": "string",
      "value": "[resourceGroup().id]"
    }
  }
}

UPDATE 2

This is the working example with objects instead of an array. I verified it locally with PowerShell by uploading the collector.json and transform.json to http://myjson.com and include them as properties when running the deploy.

parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "azureEnvironment": {
      "value": "#{azureEnvironment}"
    },
    "locationKeys": {
      "value": "#{locationKeys}"
    },
    "locationNames": {
      "value": "#{locationNames}"
    },
    "transformTemplateLink": {
      "value": "#{transform}"
    },
    "collectorTemplateLink": {
      "value": "#{collector}"
    }
  }
}

deploy.json

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "appName": {
      "type": "string",
      "defaultValue": "myApp"
    },
    "azureEnvironment": {
      "type": "string"
    },
    "locationKeys": {
      "type": "array"
    },
    "locationNames": {
      "type": "array"
    },
    "transformTemplateLink": {
      "type": "string",
      "defaultValue": "[uri(deployment().properties.templateLink.uri, 'transform.json')]"
    },
    "collectorTemplateLink": {
      "type": "string",
      "defaultValue": "[uri(deployment().properties.templateLink.uri, 'collector.json')]"
    }
  },
  "resources": [
    {
      "apiVersion": "2015-01-01",
      "name": "collector",
      "type": "Microsoft.Resources/deployments",
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[parameters('collectorTemplateLink')]",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "transformTemplateUri": {
            "value": "[parameters('transformTemplateLink')]"
          },
          "locationNames": {
            "value": "[parameters('locationNames')]"
          }
        }
      }
    },
    {
      "name": "[concat(parameters('appName'), '-dash-', parameters('azureEnvironment'))]",
      "type": "Microsoft.Portal/dashboards",
      "apiVersion": "2015-08-01-preview",
      "location": "westeurope",
      "tags": {
        "hidden-title": "[concat('24/7 Operations - (', parameters('azureEnvironment'), ')')]"
      },
      "properties": {
        "lenses": {
          "0": {
            "order": 0,
            "parts": "[reference('collector').outputs.result.value]"
          }
        }
      },
      "dependsOn": [
        "collector"
      ]
    }
  ],
  "outputs": {
    "resourceGroupId": {
      "type": "string",
      "value": "[resourceGroup().id]"
    }
  }
}

collector.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "transformTemplateUri": {
      "type": "string"
    },
    "locationNames": {
      "type": "array"
    }
  },
  "variables": {
    "count": "[length(parameters('locationNames'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2015-01-01",
      "name": "loop-0",
      "properties": {
        "mode": "Incremental",
        "parameters": {},
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {},
          "variables": {},
          "resources": [],
          "outputs": {
            "collection": {
              "type": "object",
              "value": {}
            }
          }
        }
      }
    },
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2015-01-01",
      "name": "[concat('loop-', copyIndex(1))]",
      "copy": {
        "name": "iterator",
        "count": "[variables('count')]",
        "mode": "serial"
      },
      "dependsOn": [
        "[concat('loop-', copyIndex())]"
      ],
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[parameters('transformTemplateUri')]"
        },
        "parameters": {
          "state": {
            "value": "[reference(concat('loop-', copyIndex())).outputs.collection.value]"
          },
          "index": {
            "value": "[copyIndex()]"
          },
          "locationNames": {
            "value": "[parameters('locationNames')]"
          }
        }
      }
    }
  ],
  "outputs": {
    "result": {
      "type": "object",
      "value": "[reference(concat('loop-', variables('count'))).outputs.collection.value]"
    }
  }
}

transform.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "state": {
      "type": "object",
      "defaultValue": {}
    },
    "index": {
      "type": "int"
    },
    "locationNames": {
      "type": "array"
    }
  },
  "variables": {
    "instance": {
      "[string(parameters('index'))]": {
        "position": {
          "x": "[mul(parameters('index'), 4)]",
          "y": 0,
          "colSpan": 4,
          "rowSpan": 1
        },
        "metadata": {
          "inputs": [],
          "type": "Extension/HubsExtension/PartType/MarkdownPart",
          "settings": {
            "content": {
              "settings": {
                "content": "[concat('# ', parameters('locationNames')[parameters('index')])]",
                "title": "",
                "subtitle": ""
              }
            }
          }
        }
      }
    }
  },
  "resources": [],
  "outputs": {
    "collection": {
      "type": "object",
      "value": "[union(parameters('state'), variables('instance'))]"
    }
  }
}
1

1 Answers

4
votes

if you just try the same (but simplified) approach in the variables section you would get this error:

The variable name cannot be an expression.

I think the same applies everywhere, you cant use expression to calculate property name, only value. having said that, if you want to create a loop you'd need to use nested deployment as an iterator. you will pass needed pieces inside\assemble them there based on input and create a resulting array:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {
        "locations": [
            "eastus",
            "westeurope"
        ],
        "eastus": {
            "red": 1
        },
        "westeurope": {
            "blue": 2
        },
        "empty": []
    },
    "resources": [
        {
            "apiVersion": "2017-05-10",
            "name": "[concat('iterator-', copyIndex(1))]",
            "type": "Microsoft.Resources/deployments",
            "copy": {
                "name": "someName",
                "count": "[length(variables('locations'))]"
            },
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "https://paste.ee/r/t5ca9/0"
                },
                "parameters": {
                    "source": {
                        "value": "[variables(variables('locations')[copyIndex()])]"
                    },
                    "state": {
                        "value": "[if(equals(copyIndex(), 0), variables('empty'), reference(concat('iterator-', copyIndex())).outputs.collection.value)]"
                    }
                }
            }
        }
    ],
    "outputs": {
        "results": {
            "type": "array",
            "value": "[reference(concat('iterator-', string(length(variables('locations'))))).outputs.collection.value]"
        }
    } }

and your iterator looks like this:

{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "source": {
            "type": "object"
        },
        "state": {
            "type": "array",
            "defaultValue": []
        }
    },
    "resources": [],
    "outputs": {
        "collection": {
            "type": "array",
            "value": "[concat(array(parameters('source')), parameters('state'))]"
        }
    }
}

ps. that link is temporary (1 month), but you can upload the iterator template anywhere and just hardcode, it contains no secrets\no data. pps. easier way - construct this outside the template (powershell\python\whatever) and pass as a parameter inside the tempate