Conditions in ARM Templates - The Right Way!

At this months Build conference there where lot’s of new Azure announcements and in particular lots of new features for Azure Resource Manager (ARM) templates. Ryan Jones. PM on ARM templates, did a breakout session talking about all this new functionality which is available now on channel 9. I want to focus on one of the big improvements, at least from perspective, and that is we now have proper conditional logic in ARM templates! I blogged a few weeks ago about a workaround that would let you have this sort of logic using nested templates, but this was very much a temporary solution. With this new functionality conditions are now fully implemented into the language!

Condition

The ARM template language now includes the “condition” key, which can be applied to a resource to determine whether or not that resource is executed. Lets take the example from the previous post again, where we wish to deploy a network card and we want to determine whether this NIC has a public IP or not via the means of a parameter. In this template, we supply a parameter called “NetworkInterfaceType” that can be “Public” or “Private”. In this template, we always deploy a vNet, and then the next step is to create a public IP resource. We only want this Public IP to exist if the “NetworkInterfaceType” is “Public”, so we add a condition to the Public IP resource:

{
    "apiVersion": "2017-04-01",
    "condition": "[equals(parameters('NetworkInterfaceType'),'Public')]",
    "type": "Microsoft.Network/publicIPAddresses",
    "name": "[Concat(variables('NICName'),'-pip')]",
    "location": "[resourceGroup().location]",
    "tags": {
        "displayName": "[Concat(variables('NICName'),'-pip')]"
    },
    "properties": {
        "publicIPAllocationMethod": "[parameters('IPAllocationMethod')]",
        "dnsSettings": {
            "domainNameLabel": "[Concat(variables('NICName'),'-pip')]"
        }
    }
},

Very simple, we use the “condition” key, and supply a value that can be evaluated to true or false. In this case whether the parameter “NetworkInterface” equals the word “Public”. If it does we create this resource, if it does not we don’t. One thing to note is that if your condition evaluates to false then the system simply skips, or no-ops the section, it is still in the template, it still gets compiled (or whatever ARM templates do behind the scenes). This is important if you are doing something like new or existing, where subsequent resources will have a dependency on your conditional resource, they do still exist in the template so this won’t error.

Ok, so we now are only creating the Public IP when the parameter “NetworkInterfaceType” is set to “Public”, but we also need to control whether or not we assign the public IP to a NIC based on the parameter, this is where it is a little bit more complicated (although not much). As I mentioned, you apply the condition at the resource level, you can’t apply it to sections inside the resource. So what we can’t do is have a single network card resource and choose which child sections are run. What we instead have to do is create two network card resources and set the condition to control which one actually gets created. One thing to note is that you can’t have the “name” property be the same for both NICS. Even though you will only create one or the other, the template still needs to compile with both in and it will throw an error if they are named the same. To get round this I have used the conditional parameter value in the name, this means later when you come to assign the NIC to a VM you can simply do something like json [concat(variables(‘NICName’),’-‘,parameters(‘NetworkInterfaceType’))]

{
    "apiVersion": "2017-04-01",
    "condition": "[equals(parameters('NetworkInterfaceType'),'Public')]",
    "type": "Microsoft.Network/networkInterfaces",
    "name": "[concat(variables('NICName'),'-public')]",
    "location": "[resourceGroup().location]",
    "tags": {
        "displayName": "parameters('NICName')"
    },
    "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('NICName'),'-pip')]"
    ],
    "properties": {
        "ipConfigurations": [
            {
                "name": "ipconfig1",
                "properties": {
                    "privateIPAllocationMethod": "[parameters('IPAllocationMethod')]",
                    "subnet": {
                        "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('NetworkName')), '/subnets/',variables('Subnet1Name'))]"
                    },
                    "publicIPAddress": {
                        "id": "[resourceId('Microsoft.Network/publicIPAddresses',Concat(variables('NICName'),'-pip'))]"
                    }
                }
            }
        ]
    }
},
{
    "apiVersion": "2017-04-01",
    "condition": "[equals(parameters('NetworkInterfaceType'),'Private')]",
    "type": "Microsoft.Network/networkInterfaces",
    "name": "[concat(variables('NICName'),'-private')]",
    "location": "[resourceGroup().location]",
    "tags": {
        "displayName": "parameters('NICName')"
    },
    "dependsOn": [],
    "properties": {
        "ipConfigurations": [
            {
                "name": "ipconfig1",
                "properties": {
                    "privateIPAllocationMethod": "[parameters('IPAllocationMethod')]",
                    "subnet": {
                        "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('NetworkName')), '/subnets/',variables('Subnet1Name'))]"
                    }
                }
            }
        ]
    }
}

Again, if NetworkInterfaceType is set to “Public” we create the first NIC which has a public IP, and if it is set to “Private” we create the second.

The full source code for this example, to deploy vNet, Public IP and Network card can be found here.

Functions

To go along with conditions, we also have a load of new functions that help with creating our conditional statements:

These are fairly self explanatory, and can be used either on their own or in combination with other functions, for example

"[greater(length(parameters('NetworkInterfaceType')),6)]"

Would be true if  the length of the string “NetworkInterfaceType” is greater than 6 characters.

Ryan also announced a number of set based functions that could also be useful for conditions:

  • contains()
  • empty()
  • intersection()
  • union()
  • first()
  • last()
  • indexOf()
  • lastIndexOf
  • startsWith()
  • endsWith()
  • max()
  • min()
  • range()

Summary

This is a significant improvement in the ARM language and adds a lot more flexibility to templates. It removes the need for nested templates purely for conditions (although they are still a good idea for modularity) and provide a lot more types of conditions than could have been achieved with this method.

I would encourage you to view the presentation from build with all the new changes to the ARM language, which also include serial copies, cross resource group deployments and conversions, as well as new managed applications. I will be covering many of these over the next few weeks.