Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Dynamic ARM Templates With Inline Logic Operators

DZone's Guide to

Dynamic ARM Templates With Inline Logic Operators

If statements have come to ARM templates! See how you can use inline logic in addition to conditions to bring more logic to your templates.

· Cloud Zone
Free Resource

Are you joining the containers revolution? Start leveraging container management using Platform9's ultimate guide to Kubernetes deployment.

A while back, I wrote an article talking about the new “Condition” option in Azure Resource Manager (ARM) templates. This was the first step into conditional logic in ARM templates and worked great for where you needed to apply a condition at the resource level. Where it fell down was when you needed a condition inside a resource. This resulted in you having to duplicate objects with different settings and working around issues like duplicate naming. In our example, we looked at whether a network card should have a public IP. The condition worked great for determining whether to create the Public IP (PIP) object, but when it came to deciding whether to assign the PIP to the network card, we ended up having to create two network card objects, one with one without, and using the condition to select which one to use — a bit painful!

I’m happy to say that a new update to the ARM template specification adds more options for conditional logic in your ARM templates. In particular, alongside conditions, we now have IF statements that you can use inline in your code to conditionally make choices at runtime, which can be really powerful. The syntax of this is pretty straightforward:

[if(condition, true value, false value)]


These if statements can be applied to parameters, variables, and, most importantly, resource properties.

At the present time, these new features haven’t rolled out to all regions, but they will over the next few days. If you want to try it out, it is definitely in the West Central US region.

Example

Let’s look at an example. If we take our previous template for deploying a network card and deciding whether to use a public IP, we can now use an IF statement in our network card properties section to decide whether to try and assign the public IP to the VM:

"publicIPAddress": "[if(equals(parameters('NetworkInterfaceType'),'Public'),variables('publicIP1'),'-pip')), json('null'))]"


We're still using the PublicIPAddress property, but instead of just providing the resource ID for the Public IP resource, we are wrapping it in an if statement. We're checking whether our “NetworkInterfaceType” is equal to “Public” (and you can use any of the other comparison functions here). If it is set to “public”, then we tell it to add the public IP. If not, we set it to Null. Because the public IP object is fairly complex, we are not including it inline. Instead, we reference a variable that contains it. This is what the variable looks like:

"publicIP1": {
    "id": "[resourceId('Microsoft.Network/publicIPAddresses',Concat(variables('NICName'),'-pip'))]"
}


Note that the name of this variable does not need to be “publicIPAddress”. All we are defining here is a variable to hold the public IP configuration that we will substitute in later. If the true condition of our if statement was a simple string, we could have included it inline, like:

"[if(equals('a', 'a'), 'yes', 'no')]"


So now, we can throw away our duplicated network card and have a single network card with an inline condition. At the same time, we are still making use of the condition statement in our PIP resource to determine whether that resource exists at all. Here is the full template that creates the PIP (or not) and the NIC. As you can see, it is much simpler and easier to understand:

    {
        "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "NetworkInterfaceType": {
            "type": "string",
            "metadata": {
                    "description": "Whether to have a public or private NIC"
                },
                "allowedValues": [
                "Public",
                "Private"
                ]
            },
            "IPAllocationMethod": {
                "type": "string",
                "defaultValue": "Dynamic",
                "allowedValues": [
                "Dynamic",
                "Static"
                ]
            },
            "PIPCount":{
                "type":"int",
                "defaultValue":1
            }
        },
        "variables": {
            "NetworkName": "lambda-vnet",
            "Subnet1Name": "lambda-subnet1",
            "Subnet2Name": "lambda-subnet2",
            "NICName":"lambdanic1",
            "PublicIPName":"[concat(variables('NICName'),'-pip')]",
            "publicIP1": {
                    "id": "[resourceId('Microsoft.Network/publicIPAddresses',Concat(variables('NICName'),'-pip'))]"
                }
        },
        "resources": [

            {
                "apiVersion": "2015-06-15",
                "type": "Microsoft.Network/virtualNetworks",
                "name": "[variables('NetworkName')]",
                "location": "[resourceGroup().location]",
                "tags": {
                    "displayName": "[variables('NetworkName')]"
                },
                "properties": {
                    "addressSpace": {
                        "addressPrefixes": [
                            "10.0.0.0/16"
                        ]
                    },
                    "subnets": [
                        {
                            "name": "[variables('Subnet1Name')]",
                            "properties": {
                                "addressPrefix": "10.0.0.0/24"
                            }
                        },

                        {
                            "name": "[variables('Subnet2Name')]",

                            "properties": {
                                "addressPrefix": "10.0.1.0/24"
                            }
                        }
                    ]
                }
            },

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

                "apiVersion": "2017-04-01",
                "type": "Microsoft.Network/networkInterfaces",
                "name": "[variables('NICName')]",
                "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":  "[if(equals(parameters('NetworkInterfaceType'),'Public'), variables('publicIP1'), json('null'))]"      
                            }
                        }
                    ]
                }
            }



        ],
        "outputs": {}
    }


The full templates for this example are also on GitHub

Other Logical Operators

In addition to IF statements, logic operators for AND, OR, and NOT have been added to the language specification.

[and(value1, value2)]

[or(value1, value2)]

[not(value1)]


Combining these with conditions or IF statements, we can use multiple criteria for defining whether something is deployed. If we take the Network card example, we can introduce a new parameter that indicates the number of public IPs to create, and only assign a public IP to a NIC if NetworkInterfaceType is set to “Public” and PIPCount is greater than 0:

"publicIPAddress":  "[if(and(equals(parameters('NetworkInterfaceType'),'Public'),greater(parameters('PIPCount'),0)), resourceId('Microsoft.Network/publicIPAddresses',Concat(variables('NICName'),'-pip')), json('null'))]"


This works, but it can be a bit confusing to read, so what you might want to do is split your condition out into a variable and then use that in your resource, e.g.:

      "variables": {
             ...
            "requirePublicIP":"[and(equals(parameters('NetworkInterfaceType'),'Public'),greater(parameters('PIPCount'),0))]",
            "publicIP1": {
                    "id": "[resourceId('Microsoft.Network/publicIPAddresses',Concat(variables('NICName'),'-pip'))]"
                }
        },


Finally, we also now have the bool() function, which can transform a parameter into a boolean, which is useful to use in our logical operators.

  "trueString": {
            "value": "[bool('true')]",
            "type" : "bool"
        },


Summary

This new set of language features is a great addition to the toolbox for building more dynamic and reusable templates. It’s not perfect — if you're using IFs with complex JSON objects, you end up having to store them as variables and end up growing that out very quickly, especially if you want to use an IF statement to decide between two different complex objects. Additionally, if you want to do something like if, if then, else, you end up having to nest multiple IFs, which can get pretty complicated to read. All of these compromises, however, are still better than having to duplicate whole objects like we did before, and we now have a pretty powerful set of tools to be able to control what we deploy dynamically at run time, and most importantly help us make scripts that are generic enough to share around.

It should be noted that if statements don’t do away with the need for conditions, but more likely, you will use these two in combination like we have here. Where you need to determine whether an entire resource is deployed, use conditions. Where you need to alter inline elements, use an if statement.

Using Containers? Read our Kubernetes Comparison eBook to learn the positives and negatives of Kubernetes, Mesos, Docker Swarm and EC2 Container Services.

Topics:
cloud ,microsoft azure ,arm templates ,logic ,tutorial

Published at DZone with permission of Sam Cogan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}