So far in this series, you learned how to use VS Code and ARM Tools extension to get started with the ARM template development, how to perform a subscription scoped deployment to create a resource group and then looked at resource group scoped deployment to create a storage account and a virtual network resources. You learned about parameterizing the ARM template so that it becomes reusable. Equipped with that knowledge, you can now start digging into expressions and variables in the ARM templates.
You have already used expressions in the template you built in the last part. For example,
[parameters('storageAccountSku')] is an expression that you used. Within an ARM template, expressions are a way to dynamically determine values of resource properties and other artifacts. Expressions are invoked within square brackets and are calculated at deployment time. When creating an expression, you can combine multiple template functions. For example, in the previous template that provisioned a storage account and a virtual network, the storage account name has to be unique. Now, either you can let the end user figure out a unique string for this resource property by trial and error or you can use an expression such as
[concat('sacct', '-', uniqueString(resourceGroup().id))] to generate a random string to use as a name for the storage account you want provision. Remember that, within the same resource group, the value generated by this expression will always be same since the seed to the
uniqueString() function will always be same. So, when you deploy a template that uses this expression multiple times, no action will be taken if the storage account already exists with the name.
There are several categories of standard (built-in) template functions that you can use to simplify template authoring and deployment experience. These functions include comparison and logical functions, numeric and string functions, array, object, and date functions, and resource and deployment value functions. For example, the
concat() function that you saw in the earlier example is a part of string functions category. It takes multiple strings and returns a concatenated string. A
concat() function exists in the array functions category as well and works on arrays and returns a concatenated array. The
uniqueString() function is in the string functions category and generates a hash based on the value passed as its argument. The
resourceGroup() function is a part of the resource functions category and returns the properties of the resource group within which the deployment is in progress.
Let us see another example of using expressions in ARM templates. In the template that was develope d in the last part, the
storageAccountName parameter was used the capture the input for the name property of the storage account resource. In this parameter definition,
maxLength elements were used to ensure the string length is between 3 and 24 characters. However, the name of the storage account name should always be in lower case. There is no element in parameter definition to describe this validation. This can be achieved by using the
toLower() function from the string functions category to transform the user provided input value into a lower case string. Here is how it can be done.
These are just a few examples of how you can use standard template functions in ARM templates as a part of different expressions. I recommend that you read through the documentation to understand how to use these functions in an expression and explore how you can simplify the template deployment experience. But, how do you evaluate an expression that you want to use in an ARM template?
Waiting for a real resource template deployment may be not be a great idea and a way to evaluate expression output without initiating a real deployment would be beneficial. This is totally possible with the
outputs element in an ARM template.
As you learned in the template basics, the
outputs element in a template is used to return values from after a deployment is complete. This is especially useful in automated deployment processes. Except for a few that are runtime functions such as
reference(), this method can be used with most of the standard template functions and expressions that you create. In general, the following snippet shows the syntax of
This is a simplified syntax of
outputs element. There are other properties you can use but for now, this is good enough.
|Element Name||Description||Is Required|
|output-name||Name of the output value||Yes|
|type||type of the output value||Yes|
|value||Expression that should be evaluated and returned as output value||No|
To try this method of using
outputs element , you can create an ARM template with no resource definitions as shown below.
In the outputs element definition, the name of the output value has been set to
lowercaseStorageAccountName and the output value is set to
[toLower(parameters('StorageAccountName'))]. Although there is just one output from this example template, there can be any number of outputs from a given ARM template. The above ARM template can be deployed using of the known deployment tools to see the value the expression evaluates to. Let us see an example using Azure CLI.
In the command above, inline parameter specification has been used and the value for the
storageAccountName has been set to a string containing mixed case characters. The expression used in the output value element should return this string with all characters in lower case.
When you deploy using Azure CLI or Azure PowerShell, the command output contains the deployment output as well when the template deployment completes successfully. The default output format in Azure CLI is JSON. So, when the command completes successfully, you will see the JSON string returned to the command console.
This JSON output can be queried using JMESPath. All you have to do is append
--query properties.outputs.<output_value_name>.value to the above Azure CLI deployment command.
Output values from the template’s
outputs element can be seen in the Azure portal as well if you navigate to the resource group and check the deployment history.
Variables are a way to help reduce complexity in an ARM template. In the template that deployed a storage account and a virtual network, we used seven parameters to collect the input required for the template deployment. This provides flexibility in choosing desired values for the deployment. While this flexibility is good, you may want to use fixed address prefixes for virtual network and subnets that you deploy as a part of the template. One way to achieve this is to hard code these values in resource properties. Hard coding values results in repetition and any unintentional mistakes that lead to errors in deployment. Also, there may be certain resource property values that you want to auto-generate based on the value of a specific parameter. This is where variables in ARM template syntax play a role.
Here is the simplified syntax for adding variables in an ARM template.
Simple variable declaration is straightforward. You just need to add a key-value pair for the variable name and it’s value. Here is how you may simplify the earlier template.
This variable definition eliminates the need for three parameters -
Accessing variable values
Within the resource definitions, you can access the values of variables using the
variables() function in an expression. Here is the updated template that uses variables for a few resource properties.
You can deploy this template by clicking on the deploy to Azure button.
In the portal template deployment experience, you will see that the number of input parameters have reduced but the final result stays same. You can try deploying this template via the Azure CLI using the same set of commands you tried in the last part.
Free-form vs known configuration
So far in this series, you have learned about are parameters, variables, and learn to use them in ARM templates. Parameters enable the flexibility to gather input from user deploying the template. You can parameterize a complete template and let the user provide every value needed for the deployment as an input parameter. This is called free-form configuration. However, using free-form configuration is not always scalable. The number of parameters that a user need to input might be overwhelming. Also, you don’t want your end user deploying this template to mess-up with critical resource properties such as virtual network and subnet address prefixes. This can have negative effects on the overall infrastructure deployment. As you have seen in the updated template, you might always want to use fixed values for certain parameters where there is scope for errors. Some of this cannot be controlled unless you use known configurations. Known configurations help standardize resource configurations. Use variables in the template for resource settings that require no user input or you need control over. By using variables, you can simply change the value of the variable if at all you need to modify the template instead of trying to find where all the value is used.
Expressions syntax in ARM templates enable combining multiple standard functions provided by the ARM template language. Variables when combined with expression syntax can simplify the overall template authoring experience. Choosing between free-form vs known configurations for the ARM template, you can create a template that is both flexible and implements known configurations to control how the deployed infrastructure gets configured. With this knowledge around parameters, variables, and expressions, in the next part of this series, you will learn more about some of the standard functions and learn how to implement user-defined functions.Share on: