0

We're trying to create templates using objects for parameters so there's the option of having multiple values in different resources, i.e. deploy an Event Hub namespace that could have multiple authorization rules and eventhubs, but another object in the parameters for a second Event Hub namespace that may only have one of each.

The template is like below:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "eventhubs": {
      "type": "object",
      "metadata": {
        "description": "JSON object that describes the deployment. see example parameters file"
      }
    }
  },
  "variables": {
      "resourceNamePrefix": "[substring(resourceGroup().name, 0, 8)]",
      "datacenterCode": "[substring(resourceGroup().name, 0, 3)]",
      "productCode": "[substring(resourceGroup().name, 3, 3)]",
      "environmentLevel": "[substring(resourceGroup().name, 6, 2)]"
  },
"resources": [
  {
    "type": "Microsoft.EventHub/namespaces",
    "name": "[concat(variables('resourceNamePrefix'), parameters('eventhubs').instances[copyIndex()].name)]",
    "apiVersion": "2015-08-01",
    "location": "[resourceGroup().location]",
    "sku": {
      "name": "[concat(variables('resourceNamePrefix'), parameters('eventhubs').instances[copyIndex()].sku.name)]",
      "tier": "[parameters('eventhubs').instances[copyIndex()].sku.tier]",
      "capacity": "[parameters('eventhubs').instances[copyIndex()].sku.capacity]"
    },
    "copy": {
      "name": "eventHubCopy",
      "count": "[length(parameters('eventhubs').instances)]"
    },
    "properties": {
      "serviceBusEndpoint": "[concat('https://',variables('resourceNamePrefix'), parameters('eventhubs').instances[copyIndex()].name,'.servicebus.windows.net:443/')]",
      "enabled": "[parameters('eventhubs').instances[copyIndex()].properties.enabled]"
    },
    "resources": [
        *** PARAMETER OBJECT ***
    ]
    "dependsOn": []
      }
    ],
    "outputs": {}
  }

And the parameters file:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
  "parameters": {
    "eventhubs": {
      "value": {
        "instances": [
          {
            "name": "EVT001",
            "sku": {
              "name": "Standard",
              "tier": "Standard",
              "capacity": 4
            },
            "scale": null,
            "properties": {
              "enabled": "true"
            },
            "resources": [
              {
                "type": "AuthorizationRules",
                "name": "SendKey",
                "apiVersion": "2015-08-01",
                "properties": {
                  "rights": [
                    "Send"
                  ]
                }
              },
              {
                "type": "AuthorizationRules",
                "name": "ListenKey",
                "apiVersion": "2015-08-01",
                "properties": {
                  "rights": [
                    "Listen"
                  ]
                }
              },
              {
                "type": "EventHub",
                "name": "TestHub",
                "apiVersion": "2015-08-01",
                "properties": {
                  "messageRetentionInDays": 7,
                  "status": "Active",
                  "partitionCount": 4
                }
              }
            ]
          },
          {
            "name": "EVT002",
            "sku": {
              "name": "Standard",
              "tier": "Standard",
              "capacity": 4
            },
            "scale": null,
            "properties": {
              "enabled": "true"
            },
            "resources": [
              {
                "type": "AuthorizationRules",
                "name": "SendKey",
                "apiVersion": "2015-08-01",
                "properties": {
                  "rights": [
                    "Send"
                  ]
                }
              },
              {
                "type": "EventHub",
                "name": "TestHub",
                "apiVersion": "2015-08-01",
                "properties": {
                  "messageRetentionInDays": 7,
                  "status": "Active",
                  "partitionCount": 4
                }
              },
              {
                "type": "EventHub",
                "name": "SecondHub",
                "apiVersion": "2015-08-01",
                "properties": {
                  "messageRetentionInDays": 7,
                  "status": "Active",
                  "partitionCount": 4
                }
              }
            ]
          }
        ]
      }
    }
  }
}

What I'm attempting to do is move the content of the resources array in the parameters file into the nested resources array in the template file. This is possible when moving an array into an object, but I'm facing the following problems with an array into an array:

"resources": "[parameters('eventhubs').instances[copyIndex()].properties]", <--- value must be of type array
"resources": [ { "[parameters('eventhubs').instances[copyIndex()].properties]" } ],  <--- expecting a name and value as it's in an object 
 "resources": [ "[parameters('eventhubs').instances[copyIndex()].properties]" ], <--- value must be of the following types: object

Adding another set of square brackets around the object in the array in the parameters file doesn't help either.

Same errors when using the createArray function.

The workaround I have is to do

    "resources": [
  {
    "type": "AuthorizationRules",
    "name": "[parameters('eventhubs').instances[copyIndex()].resources[0].name]",
    "apiversion": "[parameters('eventhubs').instances[copyIndex()].resources[0].apiversion]",
    "properties": "[parameters('eventhubs').instances[copyIndex()].resources[0].properties]",
    "dependsOn": [ "[concat(variables('resourceNamePrefix'), parameters('eventhubs').instances[copyIndex()].name)]" ]
  }
],

But the type property cannot be an expression so won't work for the way our templates will be consumed and used.

Is it possible to do what I'm attempting?

4
  • i doubt you can achieve what you want + your parameter file looks like a template already, that makes no sense. you should rethink what you are doing. you are deliberately making you life harder, why? Commented Nov 2, 2017 at 16:36
  • these parameter files will be consumed by other teams in our company. We want to define certain, standard values in the template that won't be changeable, and configurable values in the parameters file. It also means we don't need to manage and re-consume a load of other template files that could have different properties and layouts, just the parameter files that (should) all work the fixed template file. If I had my way we wouldn't even be using ARM for this but wcyd Commented Nov 2, 2017 at 17:27
  • not using arm templates for big\complex deployments in Azure is literally the stupidiest thing you can do. anyway, you can achieve your endgoal, just not with this approach Commented Nov 2, 2017 at 17:51
  • What would you suggest? Using objects is one of the recommendations in the MS documentation for larger deployments where you may run out of parameters and the templates themselves need to be consumable by multiple different projects so trying to t-shirt size them is difficult. This in theory should work because it does with arrays into objects, it's just the validation failing because it's expecting an array before substituting it in from the parameter expression Commented Nov 2, 2017 at 19:21

1 Answer 1

1

this won't really fit into an answer (and i, sadly, dont have time to type out all the explanations), but you are basically looking to separate configuration and implementation (which is a wise thing to do), but ARM templates are not as straight forward as you would think. I will try to create a mcve. It will be really abstract, but you only need the idea.

So the premise: you need to deploy X amount of resource with different properties.

you config data looks like this:

"eventhubs": [
    {
        "name": "EVT001",
        "sku": skuobject,
        "scale": null,
        "properties": propertiesobject,
        "resources": [
            nestedobject,
            nestedobject,
            nestedobject
        ]
    },
    parentobject,
]

This object is nested object inside array inside parent object inside array (i suggest you drop the outermost object, as it is useless and does nothing except for adding complexity). Your course of action, iterate outermost (parent object) array in a copy loop and attempt to iterate innermost (nested object) array in the properties of the resource. which doesnt work for ARM templates.

With arm templates you can only iterate one level deep (with a loop, with hardcode you can go as deep as you want) so what you need to do is split your configuration object into objects that are 1 level deep. your object is 2 levels deep (array of parent object and array of nested objects). you can do that with a nested deployment.

What you need to do is something like this:

{
    "name": "[concat('nested-', copyIndex())]",
    "type": "Microsoft.Resources/deployments",
    "apiVersion": "2016-06-01",
    "copy": {
        "name": "eventHubLoop",
        "count": "[length(parameters('eventhubs'))]" (this should be an array)
    },
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri": "xxx",
            "contentVersion": "1.0.0.0"
        },
        "parameters": {
            "currentIteration": {
                "value": "[parameters('eventhubs')[copyIndex()]]"
            }
        }
    }
}

and your nested template should be parent resource (no copy, as it is always a single resource, we took care of that with the loop above) and properties\resources loop. unfortunately i dont have an example handy (nda), but i hope you get the idea. you need to create 2 distinct loops, that way you can handle your level of nestedness, if you have 3 levels of nestedness, you need 3 distinct loops (you can achieve that the same way)

you can of course make this much more complex and parametrize almost anything (location, different resource groups, different additional properties), but i feel like this level of complexity doesnt add anything to the table but rather makes you create a properties file exactly as a template (and this is the point where it gets useless if you ask me).

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for taking the time to respond. I'll have a look into it. The only thing I'd point out is "... attempt to iterate innermost (nested object) array in the properties of the resource." isn't quite correct. I'm not wanting to iterate the inner-most loop, only to substitute that array into the resources array in the template. The outer-most loop will handle the iterations. We do the same thing with other templates that substitute an array of objects into an object (access policies in a keyvault for example)
yeah, sorry, you are not trying to iterate it, but i believe that's the only way to do that, is to iterate. i couldn't find a way to pass object\arrays to resources node directly. you can do that with properties or a resource (access policies in a keyvault), but not with the whole node.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.