Azure Resource Manager child resources: a style guide

Recently, I needed to create an Azure Resource Manager (ARM) template for a virtual network (Vnet) containing numerous configuration items. Because this Vnet is designed to peer to a virtual network gateway, one of the subnets must have the fixed name “GatewaySubnet.”

Microsoft’s ARM template documentation lacks a comprehensive set of basic coding style recommendations about when to use what capability. While the doc for what ARM coding can accomplish is (slowly) getting better, how to do what you might want is limited to a few samples on Github and Google searches. These, naturally, vary in quality.

One confusing template concept is the “child” resource and when and how it can and should be used in a template. The doc on ARM child resources only provides a glancing blow description of the child resources, largely focused on the naming requirements. It’s fine, as far as it goes — which isn’t far.

But it’s no help telling you when you might want to actually use a child resource. In my case, using a child resource solved a big problem: how to automate creating “regular” subnets and create a subnet with a fixed name. The template is invoked by a PowerShell script that allocates the IP address space. Using that allocation, the script calculates the regular subnets’ addresses and names and passes in an array of hashtables. In turn, the ARM template uses a copy: statement to create the regular subnets in the assigned IP space. Here’s an example hashtable containing two subnets to be created. The script invokes the ARM template via New-AzResourceGroupDeployment and passes the hashtable as a dynamic parameter:

@(@{Name = "Subnet1"; addressPrefix = "10.172.12.64/26" },@{Name = "Subnet2"; addressPrefix = "10.172.12.128/26" })

The ARM template also creates and assigns a network security group (NSG) to those subnets. But NSGs aren’t permitted in GatewaySubnet So, the question is, how do you create that “oddball” subnet — the one that is not like the others in the array of subnets?

This is an ideal use for a child resource. Simply embed a child resources: object array inside the Vnet’s resources: array and make sure to just use the type name as the resource you wish to create. That is, do not pass the complete resource ID to the Resource Manager.

In this case "type": "subnet" and "name": "GatewaySubnet" are enough for the Resource Manager to figure out which Vnet to place the subnet in. Interestingly, if you do not specify dependsOn with the resource ID of the Vnet you will get an error telling you to add one. IMHO, if the Resource Manager knows enough to be inform you of the exact missing dependsOn in an error message and the subnet resource is being created as a child resource, the dependsOn shouldn’t be necessary. Oh, well… To complete the child subnet definition, a parameter named gatewaySubnetAddressPrefix is used to pass the addressPrefix of the GatewaySubnet to be created.

So, after all that, we now have a coding style guideline for ARM template child resources: use a child resource when you must implement a special case in a group of resources you are creating. In this template, I created subnets and assigned an NSG to them in a copy: loop. However, since one cannot apply an NSG to the GatewaySubnet, a child resource makes it possible to handle the special case easily.

Here’s the template file. The template is (c) 2019 Air11 Technology LLC and is licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0

I hope you find this useful.

{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetName": {
            "type": "string",
            "metadata": {
                "description": "Name of the new Vnet"
            }
        },
        "vnetAddressSpace": {
            "type": "string",
            "metadata": {
                "description": "Vnet CIDR address space"
            }
        },
        "region": {
            "type": "string",
            "metadata": {
                "description": "Lowercase region into which the Vnet should be defined"
            }
        },
        "gatewaySubnetAddressPrefix": {
            "type": "string",
            "metadata": {
                "description": "String containing the gateway subnet address"
            }
        },
        "subnets": {
            "type": "array",
            "metadata": {
                "description": "Array containing subnet values for the new Vnet"
            }
        },
        "NsgExpressRouteName": {
            "type": "string",
            "defaultValue": "[concat('NsgExpressRoute', parameters('vnetName'))]",
            "metadata": {
                "description": "Example: NsgExpressRouteEastUS"
            }
        },
        "location": {
            "type": "string",
            "defaultValue": "[resourceGroup().location]",
            "metadata": {
                "description": "Location for all resources."
            }
        }
    },
    "variables": {
    },
    "resources": [
        {
            "name": "[parameters('NsgExpressRouteName')]",
            "type": "Microsoft.Network/networkSecurityGroups",
            "location": "[parameters('region')]",
            "apiVersion": "2019-04-01",
            "properties": {
                "securityRules": [
                    {
                        "name": "ExpressRoute",
                        "properties": {
                            "access": "Allow",
                            "description": "Required to permit inbound traffic from Express Route NAT IPs",
                            "destinationAddressPrefix": "*",
                            "destinationPortRange": "*",
                            "direction": "Inbound",
                            "priority": 100,
                            "protocol": "*",
                            "sourceAddressPrefixes": [
                                "1.1.1.1",
                                "1.1.1.2",
                                "1.1.1.3",
                                "1.1.1.4"
                            ],
                            "sourcePortRange": "*"
                        }
                    }
                ]
            }
        },
        {
            "name": "[parameters('vnetName')]",
            "type": "Microsoft.Network/virtualNetworks",
            "location": "[parameters('region')]",
            "apiVersion": "2019-04-01",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkSecurityGroups/',parameters('NsgExpressRouteName'))]"
            ],
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[parameters('vnetAddressSpace')]"
                    ]
                },
                "dhcpOptions": {
                    "dnsServers": [
                        "172.16.1.1",
                        "172.16.1.2",
                        "172.16.1.3",
                        "172.16.1.4"
                    ]
                },
                "copy": [
                    {
                        "name": "subnets",
                        "count": "[length(parameters('subnets'))]",
                        "input": {
                            "name": "[parameters('subnets')[copyIndex('subnets')].name]",
                            "properties": {
                                "addressPrefix": "[parameters('subnets')[copyIndex('subnets')].addressprefix]",
                                "networkSecurityGroup": {
                                    "id": "[resourceId('Microsoft.Network/networkSecurityGroups',parameters('NsgExpressRouteName'))]"
                                },
                                "serviceEndpoints": [
                                    {
                                        "service": "Microsoft.Sql"
                                    },
                                    {
                                        "service": "Microsoft.KeyVault"
                                    },
                                    {
                                        "service": "Microsoft.Storage"
                                    },
                                                                        {
                                        "service": "Microsoft.AzureActiveDirectory"
                                    }
                                ]
                            }
                        }
                    }
                ]
            },
            "resources": [
                {
                    "name": "GatewaySubnet",
                    "type": "subnets",
                    "location": "[parameters('region')]",
                    "apiVersion": "2019-04-01",
                    "dependsOn": [
                        "[resourceId('Microsoft.Network/virtualNetworks/',parameters('vnetName'))]"
                    ],
                    "properties": {
                        "addressPrefix": "[parameters('gatewaySubnetAddressPrefix')]"
                    }
                }
            ]
        }
    ],
    "outputs": {
        "NewVnetResourceId": {
            "type": "string",
            "value": "[resourceId('Microsoft.Network/virtualNetworks',parameters('vnetName'))]"
        }
    }
}

Posted

in

,

by

Tags:

Comments

2 responses to “Azure Resource Manager child resources: a style guide”

  1. Alex Batishchev Avatar

    Hi Alex,
    I wonder if you know how to create a child resource with a tag?
    What a coincidence, I’m creating exactly the same resources in my template: a vnet, subnets for VMSS, and one subnet for VNG. And I need to tag the latter with a tag.
    The syntax you’re using unfortunately doesn’t allow to specify the top-level properties of the child resources, only the “properties” properties.

    1. Alex Neihaus Avatar
      Alex Neihaus

      If I understand what you are asking, this is very simple.

      Just add Tags: {} properties to the resources with the tags you want. I added tags property to the parent resource and the child in this template. The template deployed fine.

      However, there isn’t a tags property for a subnet (even though there are for some of the child resources of Microsoft.Network virtualNetworks/subnets, like serviceEndpointPolicies.

Leave a Reply

Your email address will not be published. Required fields are marked *