Description: https://images.manning.com/360/480/resize/book/6/97b41d0-2a5b-4e9d-b4ef-c68f03644c77/Keilholz-MEAP-HI.png

From Azure Infrastructure as Code by Henry Been, Eduard Keilholz, and Erwin Staal

This is an excerpt from chapter 6, which starts with introducing the Bicep language, a new and improved IaC language for a Azure. This snippet is the next section of the chapter, which lists the benefits of Bicep over ARM Templates.

Let’s jump into using Bicep templates to improve ARM template syntax!


Take 35% off Azure Infrastructure as Code by entering fccbeen into the discount code box at checkout at manning.com.


Bicep templates are a good approach to alleviating issues with ARM templates. Apart from addressing the downsides of the existing ARM template syntax, Bicep templates also come with other improvements. Let’s have a look!

Referencing resources, parameters, and variables

One of these improvements is in referencing other resources and fetching their properties. To see the difference, let’s first revisit referencing a property using an ARM Template.

 
 [listKeys(resourceId('Microsoft.Storage/storageAccounts',
         [CA] parameters('storageAccountName')), 2019-04-01').key1]"
  

Remember how you first had to build the full resourceId for the function using its type and name? With Bicep, no more for resources that are declared in the same template. Now you can use the reference-name for the resource you saw earlier and directly access any of its properties, like this:

 
 listKeys(myStorageAccount.id, '2019-04-01').key1
  

Here you see that the listKeys(...) function is called again using two parameters. But instead of using two functions, you can directly reference the resource using its reference-name and then fetch its property id. This yields the same result as building that id manually using the resourceId(...) function. Just comparing these two code examples shows the improvement to readability.

Referencing parameters and variables works the same way as referencing a resource. If you want to refer to a parameter for setting a value, you can use it directly like this:

 
 Parameter storageAccountName string
  
 resource myStorageAccount 'Microsoft.Storage/[email protected]' = {
     name: storageAccountName
     ...
 }
  

This example declares a Storage Account. The Storage Account name is not specified as a literal but references the value of the parameter storageAccountName. One thing to note is that parameters, variables, and resources share a single namespace. Sharing a namespace means that you cannot use the same name for a parameter, variable, or resource – all names have to be unique.

The single exception are outputs. Outputs can have the same name as parameters, variables, or resources. This allows you to do something like this:

 
 var storageAccountName = 'myStorageAccount'
  
 resource myStorageAccount 'Microsoft.Storage/storageAccounts
         [CA] @2020-08-01-preview' = {
     name: storageAccountName
     location: resourceGroup().location
     kind: 'StorageV2'
     sku: {
         name: 'Premium_LRS'
         tier: 'Premium'
     }
 }
  
 output storageAccountName string = storageAccountName
  

In this example, you see a typical scenario where a resource’s name is generated using a variable value and returned as an output. Reusing the same name for the variable and the output allows for better readability over using different names for precisely the same thing.

Using References in variables and outputs

In ARM Templates, it is impossible to use the reference(...) function in variables. The reason is that ARM calculates variable values deploying any resource, and in that stage it cannot evaluate the reference(...) function. In Bicep template, this problem does no longer exists. To see why, take a look at the following Bicep template:

 
 resource myStorageAccount 'Microsoft.Storage/storageAccounts
         [CA] @2020-08-01-preview' = {
     name: 'myStorageAccount'
     location: resourceGroup().location
     kind: 'StorageV2'
     sku: {
         name: 'Premium_LRS'
         tier: 'Premium'
     }
 }
  
 var storageAccountName = myStorageAccount.name
 var accessTier = myStorageAccount.properties.accessTier
  
 output message string = '${storageAccountName} with tier ${accessTier}'
  

In both variables in the example above, a storage account is referenced. Here the Bicep transpiler does something clever. Instead of leaving these variables in and using the variable value in the output, it completely removes the variable. Instead, it copies the complete expression that yields the parameters result into the output. Like this:

 
 {
   "$schema": "https://schema.management.azure.com/schemas/
           [CA] 2019-04-01/deploymentTemplate.json#",
     "variables": {
         "storageAccountName": "myStorageAccount"
     },
     "resources": [
         {
           "type": "Microsoft.Storage/storageAccounts",
           ...
         }
     ],
     "outputs": {
         "message": {
         "type": "string",
         "value": "[format('{0} was deployed with accessTier {1}', 
               [CA] variables('storageAccountName'), reference(resourceId('Microsoft.
               [CA] Storage/storageAccounts', 'myStorageAccount')).accessTier)]"
       }
     }
 }
  

In the example above, you can see how the output’s value is built up without using the variable accessTier. Instead, the reference(...) function moves into the output’s value calculation, where it works.

Referencing existing resources

When you want to reference an existing resource on multiple occasions, you can repeatedly use the reference(...) function. But as a better alternative, you can also make that resource available in your template under a reference-name. To do this, you can use the existing keyword as follows:

 
 resource existingStorageAccount 'Microsoft.Storage/storageAccounts
         [CA] @2020-08-01-preview' = existing {                         #A
     name: 'preexistingstorageaccountname'
 }
  

#A The existing keyword indicates a pre-existing resource that should not be created using this template.

This example does not declare a new resource at all, but instead, it only builds a reference to an existing resource with the provided name. Please note that when you deploy this Bicep template, and the resource does not exist yet at that time, the deployment will result in an error.

Dependency management

Yet another improvement with the arrival of Bicep is the removal of having to declare dependencies. As you may be aware, you are required to declare all dependencies between your resources, even if they are already implicitly declared through a parent-child relationship.

With Bicep, this is not needed anymore. When you transpile a Bicep file to an ARM template, all implicit dependencies are automatically detected, and the correct dependsOn declarations automatically are generated. Implicit dependencies are parent-child relations and resources that reference each other. For example, when your App Settings reference a Storage Account to store its key as a setting, no more need to declare dependencies manually.

If you want to control the order of resource deployments, and there is no implicit dependency, you can still declare your own dependencies using the dependsOn property. The dependsOn property still works precisely the same as in ARM templates.

String interpolation

Another nuisance when building larger ARM templates is the lack of built-in support for string interpolation. String interpolation is a technique used for building text strings out of literals, parameters, variables, and expressions. For example, take a look at the following common approach for generating names for resources based on the environment name.

 
     "parameters": {
         "environmentName": {
             "type": "string",
             "allowed": [ "dev", "tst", "prd" ]
         }
     },
     "variables": {
         "mainStorageAccountName": "[concat('stor', parameters('environmentName'), 
             [CA] 'main')]",
         "otherStorageAccountName": "[concat('stor', parameters('environmentName'),
             [CA] 'other')]",
         "somethingWebAppName": "[concat('webappp', parameters('environmentName'),
             [CA] 'something')]"
     },
  

While the above works quite well, it takes a few seconds to “calculate” the actual resource name in your mind when reading the template. And that’s for this relatively simple example. Just imagine how that would work when listing tens of resources.

In Bicep templates, it is no longer necessary to use the concat(...) function for concatenating values. Instead, you can use ${...} syntax anywhere within a string literal, and the value between the curly braces is automatically interpreted as an expression. Here is an example.

 
 param environmentName string {
     allowed: [ 'dev', 'tst', 'prd']
 }
  
 var mainStorageAccountName = 'stor${environmentName}main'
 var otherStorageAccountName = 'stor${environmentName}other'
 var somethingWebAppName = 'webappp${environmentName}something'
  

As you can see, the values for the three variables are now much easier to read. Readability is greatly enhanced because it is no longer needed to call to the parameters(...) function in combination with the ${...} syntax. As a bonus, when you are using the Bicep extension for VS Code – syntax highlighting also shows where string interpolation is used to enhance readability even more.

No mandatory grouping

One of the consequences of using JSON as the basis of ARM Templates was the object notation for parameters and variables. Through this choice, all parameters and variables have to be grouped in one location of a template – usually at the start. Take a look at the following example:

 
 {
     "$schema": "https://schema.management.azure.com/schemas/
         [CA] 2019-04-01/deploymentTemplate.json#",
     "contentVersion": "1.0.0.0",
     "parameters": {
         "environment": {                                        #B
             "type": "string"
         },
                                                                 #A
     },
     "variables": {
         "webAppPlanName": "[format('myAppPlan-{0}', parameters('environment'))]",
         "anotherVariable": "someValue"
     },
     "resources": [
                                                                 #A
         {
             "type": "Microsoft.Web/serverfarms",
             "apiVersion": "2020-06-01",
             "name": "[variables('webAppPlanName')]",            #C
             "location": "westeurope"
         }
     ]
 }
  

#A Imagine there are tens or hundreds of lines at these locations

#B The declaration of a variable

#C The usage of that same variable

Especially in larger templates, this made for much scrolling up and down, as parameters were all defined at the top and used much more down in the file. JSON provides no way to work around this, and you cannot declare the parameter or variable keys more than once. With the new Bicep syntax, the grouping of parameters at the top is no longer necessary, and you can do something like this.

 
 param environment string = 'tst'
  
                                                                       #A
  
 var webAppPlanName = 'myAppPlan-${environment}'                       #B
  
 resource myWebApp 'Microsoft.Web/[email protected]' = {
     name: webAppPlanName                                              #C
     location: 'westeurope'
 }
  
                                                                       #A
  
 var anotherVariable = 'someValue'
  
                                                                       #A
  

#A Imagine there are tens or hundreds of lines at these locations

#B The declaration of a variable

#C The usage of that same variable

In this example, you see that parameters and variables are now declared just before they are used. Other variables can be declared where they are used without problems. Strictly speaking, you can even use variables before they are used as everything is eventually grouped anyhow when transpiling to an ARM template.

All of this greatly enhances both the readability and understandability of the templates.

Comments

A final improvement of the Bicep language is the introduction of comments. While ARM Templates already supports comments, this has one downside: it makes the templates no longer JSON-compliant. This leads to many text editors pointing to comments as an error. Luckily, VS Code is an exception to that rule as it understands ARM templates instead of “just” JSON. With Bicep, all of this no longer applies, and you can now use comments as follows.

 
 var webAppPlanName = 'myAppPlan' // Everything after a double slash is a comment
  
 resource myWebAppPlan 'Microsoft.Web/[email protected]' = {
     name: webAppPlanName
     location: 'westeurope'
     /*
         Using slash star you can start a multi-line comment
         Star slash closes the comment
     */
 }
  

In this example, there are two types of comments. A single-line comment is added using a // at (the end of) every line. A multi-line comment is created by enclosing it in /* and */. It is unnecessary to put the /* and */ on a line of their own, but it is the convention.

If you want to learn more about the book, you can check it out on Manning’s liveBook platform here.