ARM Template object parameters: defaults and unions

Table of contents

No heading

No headings in the article.

This is a quick post on working with object parameters in ARM templates.

ARM templates have different parameter types: string, int, bool, object, and so on.

Parameters have various attributes: their type, some metadata, and a default value.

The default value attribute is used to make a parameter optional: if you deploy the template and don't specify a value for a particular parameter, ARM will just use the default value configured on that parameter. If you don't specify a value and the parameter doesn't have a default value, you get a deployment error.

For scalar parameter types like string or int, specifying a default value is simple and intuitive: a string default value could just be "" (blank) or "foo" or whatever, including using some ARM functions. Same with int, bool, and so on. For example:

"parameters": {
  "location": {
    "type": "string",
    "defaultValue": "[resourceGroup().location]"
  },
  "doSomething": {
    "type": "bool",
    "defaultValue": false
  },
  "thingCount": {
    "type": "int",
    "defaultValue": 0
  }
}

This is very straightforward. However: when you use a parameter of type object and want its default value to be null or empty if not specified, this isn't documented (that I could find). Various tricks like double quotes, null(), json('null') and so on all fail.

For example, adding a tags parameter to allow a caller to optionally specify a set of key-value tags, but if not specified, having the value of tags be an empty or null object:

        "tags": {
            "type": "object",
            "defaultValue": "try various things here to default to an empty or null object..."
        }

results in an error like this:

{'code': 'InvalidTemplate', 'message': "Deployment template validation failed: 'Template parameter 'tags' was provided an invalid value. Expected a value of type 'Object', but received a value of type 'Null'.

So how do you do it? createObject()!

Normally, createObject() requires an even set of arguments (key and value pairs), but if passed zero arguments, it emits an empty object.

        "tags": {
            "type": "object",
            "defaultValue": "[createObject()]"
        }

This works! Now you can have ARM template parameters of type object, and make them optional for template callers, defaulting to empty objects.

In my case, I used such a tags parameter to allow a template deployment to optionally specify a set of key-value tags, then added an automatically-generated Timestamp tag so that any Azure resource deployed with this template would automatically have a Timestamp showing when the resource was deployed. (You could figure this out with the Activity log, of course, but a tag makes it easier and quicker.)

To do this, you'll first need a Timestamp parameter with its value automatically set to the current date and time. Here, I'm using UTC time for consistency across a multi-geo Azure estate. And for completeness, I'm showing both the timestamp template parameter as well as tags, which you already saw above.

"parameters": {
    "timestamp": {
        "type": "string",
        "defaultValue": "[utcNow('u')]"
    },
    "tags": {
        "type": "object",
        "defaultValue": "[createObject()]"
    }
}

And then in template variables, I'm first creating a Timestamp tag (remember a tag has to be a key-value pair) variable, and then combining any tags the caller may have specified with this Timestamp tag, using the ARM union() function.

"variables": {
    "timeStampTag": {
        "Timestamp": "[parameters('timestamp')]"
    },
    "tags": "[union(parameters('tags'), variables('timeStampTag'))]"
}

And then we set the tags variable onto the deployed resource via its tags attribute, as usual.

"tags": "[variables('tags')]"

When I call this template and don't specify a value for the tags parameter at all, I wind up with a deployed resource with just the timestamp.

Resource with just the Timestamp tag

But when I call this template and specify some tags relevant in my environment - here, I passed AutoRefresh, OSDiskName, and Classification tags plus values for each - I get all these tags plus the Timestamp tag.

Resource with specified and Timestamp tags combined

The above ARM JSON fragments are from my Azure Linux VM template. The screenshots with just the Timestamp tag, or the combined set of tags, are from VMs deployed with this template.

This is one of the modular templates I maintain in my plzm/azure-deploy GitHub repo and use in various IaC projects. See my post on modular and reusable ARM templates for more.

To recap:

  1. Use an empty [createObject()] to make an object template parameter optional and default it to an empty object if the template caller does not specify a value.
  2. You can combine automatically created objects with defaulted or specified objects.

Happy templating and IACing!