14

I have an ARM template where I set up a load balancer and I want to add a number of port openings by adding rules and probes to the LB.

This is the template I have so far:

    {
        "type": "Microsoft.Network/loadBalancers",
        "name": "LB-front",
        "apiVersion": "2016-03-30",
        "location": "westeurope",
        "tags": { },
        "properties": {
            "frontendIPConfigurations": [
                {
                    "name": "LoadBalancerIPConfig",
                    "properties": {
                        "privateIPAllocationMethod": "Dynamic",
                        "publicIPAddress": {
                            "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_lbipdev_0_name'))]"
                        }
                    }
                }
            ],
            "backendAddressPools": [
                {
                    "name": "LoadBalancerBEAddressPool"
                }
            ],
            "loadBalancingRules": [
                {
                    "name": "AppPortLBRule1",
                    "properties": {
                        "frontendIPConfiguration": {
                            "id": "[parameters('loadBalancers_LB_dev_id_6')]"
                        },
                        "frontendPort": 80,
                        "backendPort": 80,
                        "enableFloatingIP": false,
                        "idleTimeoutInMinutes": 5,
                        "protocol": "Tcp",
                        "loadDistribution": "Default",
                        "backendAddressPool": {
                            "id": "[parameters('loadBalancers_LB_dev_id_7')]"
                        },
                        "probe": {
                            "id": "[parameters('loadBalancers_LB_dev_id_8')]"
                        }
                    }
                },
                {
                    "name": "AppPortLBRule2",
                    "properties": {
                        "frontendIPConfiguration": {
                            "id": "[parameters('loadBalancers_LB_dev_id_9')]"
                        },
                        "frontendPort": 81,
                        "backendPort": 81,
                        "enableFloatingIP": false,
                        "idleTimeoutInMinutes": 5,
                        "protocol": "Tcp",
                        "loadDistribution": "Default",
                        "backendAddressPool": {
                            "id": "[parameters('loadBalancers_LB_dev_id_10')]"
                        },
                        "probe": {
                            "id": "[parameters('loadBalancers_LB_dev_id_11')]"
                        }
                    }
                },
                {
                    "name": "AppPortLBRule3",
                    "properties": {
                        "frontendIPConfiguration": {
                            "id": "[parameters('loadBalancers_LB_dev_id_12')]"
                        },
                        "frontendPort": 82,
                        "backendPort": 82,
                        "enableFloatingIP": false,
                        "idleTimeoutInMinutes": 5,
                        "protocol": "Tcp",
                        "loadDistribution": "Default",
                        "backendAddressPool": {
                            "id": "[parameters('loadBalancers_LB_dev_id_13')]"
                        },
                        "probe": {
                            "id": "[parameters('loadBalancers_LB_dev_id_14')]"
                        }
                    }
                }
            ],
            "probes": [
                {
                    "name": "AppPortProbe1",
                    "properties": {
                        "protocol": "Tcp",
                        "port": 80,
                        "intervalInSeconds": 5,
                        "numberOfProbes": 2
                    }
                },
                {
                    "name": "AppPortProbe2",
                    "properties": {
                        "protocol": "Tcp",
                        "port": 81,
                        "intervalInSeconds": 5,
                        "numberOfProbes": 2
                    }
                },
                {
                    "name": "AppPortProbe3",
                    "properties": {
                        "protocol": "Tcp",
                        "port": 82,
                        "intervalInSeconds": 5,
                        "numberOfProbes": 2
                    }
                }
            ],
            "inboundNatRules": [],
            "outboundNatRules": [],
            "inboundNatPools": []
        },
        "resources": [],
        "dependsOn": [
            "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_lbipdev_1_name'))]"
        ]
    },

(some details omitted)

What I would like to do is to have an array of the port numbers I want to create rules and probes for and loop over those instead of explicitly having to write each rule and probe as a property for the resource.

Basically I would like a parameter or variable in my template like this:

"ports": [ 80, 81, 82, ...]

and that I could loop over this similar to this: https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple.

juvchan
  • 6,113
  • 2
  • 22
  • 35
Lee G.
  • 173
  • 1
  • 1
  • 7

4 Answers4

24

Indeed you can! Copy does work with properties!

Create a parameter or variable like this (this example will use parameter array):

"lbRules": {
  "type": "array",
  "defaultValue": [
    {
      "name": "httpPort",
      "frontendPort": "80",
      "backendPort": "80",
      "protocol": "tcp"
    },
    {
      "name": "customAppPort",
      "frontendPort": "8080",
      "backendPort": "8888",
      "protocol": "tcp"
    },
    {
      "name": "httpsPort",
      "frontendPort": "443",
      "backendPort": "443",
      "protocol": "tcp"
    }
  ]
}

Use this parameter in the Loadbalancer resource using copy like this that will create that many probes and rules that you defined in your parameter array:

{
  "apiVersion": "[variables('lbApiVersion')]",
  "type": "Microsoft.Network/loadBalancers",
  "name": "[parameters('myLoadBalancer')]",
  "location": "[parameters('computeLocation')]",
  "dependsOn": [
    "[concat('Microsoft.Network/publicIPAddresses/',concat(parameters('lbIPName'),'-','0'))]"
  ],
  "properties": {
    "frontendIPConfigurations": [
      {
        "name": "LoadBalancerIPConfig",
        "properties": {
          "publicIPAddress": {
            "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('lbIPName'),'-','0'))]"
          }
        }
      }
    ],
    "backendAddressPools": [
      {
        "name": "LoadBalancerBEAddressPool",
        "properties": {}
      }
    ],

    "copy": [
      {
        "name": "probes",
        "count": "[length(parameters('lbRules'))]",
        "input": {
          "name": "[concat(parameters('lbRules')[copyIndex('probes')].name,'Probe')]",
          "properties": {
            "intervalInSeconds": 5,
            "numberOfProbes": 2,
            "port": "[parameters('lbRules')[copyIndex('probes')].backendPort]",
            "protocol": "[parameters('lbRules')[copyIndex('probes')].protocol]"

          }
        }
      },

      {
        "name": "loadBalancingRules",
        "count": "[length(parameters('lbRules'))]",
        "input": {
          "name": "[parameters('lbRules')[copyIndex('loadBalancingRules')].name]",
          "properties": {
            "frontendIPConfiguration": {
              "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('myLoadBalancer')),'/frontendIPConfigurations/LoadBalancerIPConfig')]"
            },
            "frontendport": "[parameters('lbRules')[copyIndex('loadBalancingRules')].frontendport]",
            "backendport": "[parameters('lbRules')[copyIndex('loadBalancingRules')].backendport]",
            "enableFloatingIP": false,
            "idleTimeoutInMinutes": "5",
            "protocol": "[parameters('lbRules')[copyIndex('loadBalancingRules')].protocol]",
            "backendAddressPool": {
              "id": "[concat(resourceId('Microsoft.Network/loadBalancers', parameters('myLoadBalancer')),'/backendAddressPools/LoadBalancerBEAddressPool')]"
            },
            "probe": {
              "id": "[concat(variables('lbID0'),'/probes/', parameters('lbRules')[copyIndex('loadBalancingRules')].name,'Probe')]"
            }
          }
        }
      }
    ],


    "inboundNatPools": []
  },

  }
}

More info can be found here:

HobPet
  • 632
  • 7
  • 13
  • 1
    I did this for a client a while back, and I couldn't remember the syntax. This was a great example that outlined the functionality perfectly. Thanks for sharing! – Rogala Sep 06 '20 at 11:52
  • 1
    I found https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/copy-properties to be a better resource – Ben Nov 05 '20 at 19:28
5

You can only apply the copy object to a top-level resource.

You cannot apply it to a property on a resource type, or to a child resource.

"resources": [
  {
    "type": "{provider-namespace-and-type}",
    "name": "parentResource",
    "copy": {  
      /* yes, copy can be applied here */
    },
    "properties": {
      "exampleProperty": {
        /* no, copy cannot be applied here */
      }
    },
    "resources": [
      {
        "type": "{provider-type}",
        "name": "childResource",
        /* copy can be applied if resource is promoted to top level */ 
      }
    ]
  }
] 

Source of Quotation: Deploy multiple instances of resources in Azure Resource Manager templates

You can loop over properties in ARM Template ONLY IF the copy object is applied to a top-level resource, which is in your case the "Microsoft.Network/loadBalancers", but that will also create multiple copy of the said resource.

If this is not what you want to achieve, I would recommend you to keep your existing way until ARM Template support copy object to property on a resource type in the future.

juvchan
  • 6,113
  • 2
  • 22
  • 35
  • How can I promote a resource to top level if that resource is technically a child of a "concrete" resource? E.g. WebSite as top level and WebSite/Extension as child level. Could I put the extension resource as a top level declaration and do some form of dependency to the website? – Lee G. Jan 27 '17 at 11:26
  • Thanks for the answer btw. – Lee G. Jan 27 '17 at 11:26
  • @lee-g yes you can declare the child resources on top-level simply be prepending the parent resource's type / name to the child resource's type / name. See here: https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-templates-resources#child-resources. Once you do this, you can also use property iteration again. – Torben Knerr Aug 24 '18 at 09:10
  • 1
    Not a valid answer anymore – Dmitry Gorshkov Mar 25 '20 at 13:34
3

It is now possible to loop on properties or on child resources as stated in https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple#property-iteration or in https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple#create-multiple-instances-of-a-child-resource

You can a child resource extension (e.g. WebSite/Extension) as a top-level resource by following the format for the type: {resource-provider-namespace}/{parent-resource-type}/{child-resource-type}. For instance Microsoft.Web/sites/siteextensions

You have also to reference the parent resource in the child resource by a concat. For instance: "name": "[concat('mywebsite', '/', 'myextension', copyIndex())]"

user3214451
  • 124
  • 3
1

What you want to achieve is possible with the take function. You linked the proper documentation site yourself. Go to the link you posted and check out the section "Create multiple instances when copy won't work".

in your case this would look like this:

"variables": {
    "probeArray": [                    
           {
             "name": "AppPortProbe1",
             "properties": {
                 "protocol": "Tcp",
                 "port": 80,
                 "intervalInSeconds": 5,
                 "numberOfProbes": 2
             }
           },
           {
             "name": "AppPortProbe2",
             "properties": {
                 "protocol": "Tcp",
                 "port": 81,
                 "intervalInSeconds": 5,
                 "numberOfProbes": 2
             }
           },
           {
             "name": "AppPortProbe3",
             "properties": {
                 "protocol": "Tcp",
                 "port": 82,
                 "intervalInSeconds": 5,
                 "numberOfProbes": 2
             }
           }
    ],

You then create an parameter specifying how many probes you want

"parameters": {
...
"numProbes": {
  "type": "int",
  "maxValue": 3,
  "metadata": {
    "description": "This parameter allows you to select the number of probes you want"
  }
}

Finally you use take inside the resource:

"resources": [
...
{
  "type": "Microsoft.Network/loadBalancers",
  "properties": {
      ...
      "probes": "[take(variables('probeArray'),parameters('numProbes'))]"
    },
    ...
  }
  ...
}
]

If you continue the through the documentation you will see that you can get even more crazy and combine copy and take with linked templates.

TobiWi
  • 21
  • 4
  • Doesnt this mean that I still have to declare all the probes but jut up in variablea instead? I was hoping for a way to just have to declare them in one chunk and the loop over the only thing that changes, ie the port numbers. – Lee G. Feb 01 '17 at 13:26
  • Yes you have to define the probes when you do it this was. There is no easy way to just iterate through properties with copyIndex(). – TobiWi Feb 10 '17 at 13:28