One of the key concepts and advantages of public cloud hosting is the use of Infrastructure as Code (IaC). IaC allows repeatable results and faster, simpler automation and orchestration. These can drastically reduce the time to deploy resources and provide a built-in level of quality, change control and versioning. However, a certain amount of time needs to be invested up-front to develop IaC skills to the level where you can truly realise these benefits.
Anyone who has spent serious time with Azure has very likely had exposure to ARM templates. This is the IaC format developed by Microsoft and is widely adopted as the default approach to IaC on the Azure platform.
These templates allow you to declare the objects you intend to deploy without having to run a sequence of commands to deploy them. Simply describe what resource you want to create in the file, deploy it with a single command (either in PowerShell, the Azure CLI or even the Azure portal) and Azure takes care of the hard work in the background. And it’s not just initial deployments; you can also modify existing resources. So, for example, with a quick change in a template you can change your virtual machine instance size or save money by decreasing the size of your Premium File Share.
Unfortunately, ARM templates have several disadvantages. The JSON syntax can be daunting to beginners and difficult to debug; the use of some functions such as “concatenate” and “reference” can result in extremely long and complicated lines of code. This results in templates that can be extremely hard to read, not just for others but even going back to a template you may have written yourself some time before. Also, they do not support any kind of modularity, so the ability to use repeatable chunks of code is not well provided for.
When transitioning to and working with public cloud we all want to be good cloud citizens but we also want to be spending as little time as possible writing and wrangling our code. So, any move toward simplification should be very welcome!
To this end, some months ago, Microsoft announced Project Bicep; a new Domain Specific Language to replace the current JSON-based code. Bicep is a project aimed at simplifying the language to make it easier to author and debug. It also aims to provide much better support for modularity to make sharing and reusing your code easier.
Project Bicep is not a rewrite of ARM; when you write a Bicep file you then need to compile it into an ARM template before actual deployment using the usual methods. Bicep is, in essence, an abstraction on top of ARM rather than a replacement.
A Warning
It is important to note that, at the time of writing, Bicep is in its first public release stage and is thus experimental. When installing it, there are multiple caveats given that it is not fully-featured and may be subject to future breaking changes. So, I really would not advise starting to use it in a production context at present.
With that warning in mind, let us take a closer look.
Installation
To begin using Bicep, it first needs to be installed in PowerShell. Full instructions are available. This includes an extension for Visual Studio Code which is the recommended authoring tool.
A Basic Bicep File
As a starter let us describe a simple Storage Account resource:
resource stgacct 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: 'stgacct1234' location: 'eastus' kind: 'Storage' sku: { name: 'Standard_LRS' } }
This declaration has four components:
- The “resource” keyword.
- Symbolic name: “stgacct” – this is Bicep identifier that can be used to reference the resource throughout the Bicep file. It is not the name of the resource when it is deployed.
- Type: “Microsoft.Storage/storageAccounts@2019-06-01” – this is comprised of the resource provider, the resource type and the API version.
- Properties: this is everything between “{…}” and describes the specific properties you want the resource to have. These are exactly the same as available in ARM templates.
For comparison, the equivalent ARM template would look like this:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": {}, "functions": [], "variables": {}, "resources": [ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2019-06-01", "name": "stgacct1234", "location": "eastus", "kind": "Storage", "sku": { "name": "Standard_LRS" } } ], "outputs": {} }
And, indeed, if we saved the file as “storage.bicep” and compiled it with the command “bicep build storage.bicep” this is exactly what we would get as a result.
Parameters, Variables and Outputs
As in ARM templates we can also use parameters, variables and outputs:
param location string = 'uk south' param name string = 'stgacct1234' var storageSku = 'Standard_LRS' resource stgacct 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: name location: location kind: 'Storage' sku: { name: storageSku // reference variable } } output storageId string = stg.id
The parameter and variable values declared here are just default values and can be replaced with the desired values as required. Well worth noting is how simply the output is declared using the Symbolic Name for the resource, “stg”. The equivalent ARM is:
"storageId": { "type": "string", "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" }
As you can see, Bicep is so much quicker and easier to both write and read. The Symbolic Name can also be used to retrieve run-time properties that only exist once the resource itself has been created, for example, a Storage Account’s primary endpoints.
There is also no need for a specific declaration block for parameters, variables and outputs like there are in ARM templates; you can declare them anywhere.
Finally, and importantly, the Symbolic Name replaces the explicit “dependsOn” property used in ARM templates to describe dependencies between resources. In Bicep, if you reference any property of a resource via the Symbolic Name, the equivalent “dependsOn” property will be automatically added to the compiled ARM.
String Interpolation
Other really useful Bicep concepts include string interpolation which directly replaces the “concat” function used in ARM to join strings. In ARM you would use:
{ "variables": { "storageAccountName": "[concat(parameters('namePref'), 'stgacct1234')]" } }
to join the value of the parameter “namePref” to the string “storage001”.
In Bicep this is more simply written:
var storageAccountName = '${namePref}stgacct1234'
No need for a function – it is just handled under the hood.
The Ternary Operator
Another useful ability of Bicep is to support the use of the ternary operator for conditions. This is the equivalent of the “If()” function in ARM. Here, instead of using the “storageSku” variable the example uses the ternary operator to conditionally choose a redundancy setting for the storage account using the parameter “globalRedundancy”:
param location string = resourceGroup().location param namePref string = 'storage' param storageRedundancy bool = true var storageAccountName = '${namePref}${uniqueString(resourceGroup().id)}' resource stgacct 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: storageAccountName location: location kind: 'Storage' sku: { name: storageRedundancy ? 'Standard_GRS' : 'Standard_LRS' } } output storageId string = stgacct.id
Using “globalRedundancy” this way allows a True / False choice on whether or not to switch on Geo Redundancy for our Storage Account.
The Future
As mentioned previously, Bicep is still very much in its early stages and Microsoft have already trailed a couple of the features coming soon:
Loops
ARM templates use the copy() and copyindex() properties to provide iterations and loops. For example, you can use this to create multiple virtual machines with one template without actually defining each virtual machine individually. Bicep promises to extend this functionality with other types of loops such as For and For Each.
Modules
These will be re-usable modules that completely encapsulate resources and can be shared within and between teams. Again, an extremely useful tool to help reduce the time needed to develop IaC.
Conclusion
Project Bicep promises to be a real evolution beyond ARM templates. The simpler, more intuitive syntax reduces the entry point and time spent learning to develop Azure IaC. And the promised extension of functionality over what ARM itself can provide is very enticing in itself.
At present, there is obvious functionality missing and it is far from being on a feature par with ARM. Equally the need to compile ARM templates which can then be deployed is a bit cumbersome.
However, this is just the beginning. Although I found it useful as an aid to help learn Bicep by being able to see the equivalent code in ARM. I would hope that in future Microsoft will provide the functionality to deploy Bicep directly without the intervening compilation, but time will tell.
Also, a really useful feature would be the ability to reverse compile existing ARM templates into Bicep so that we could all move our current libraries of ARM templates to the new, simpler format should we wish.
All in all, if you work with ARM templates day-to-day, are just starting out, or are just interested in where Azure and IaC is heading, I thoroughly recommend heading over to the Project Bicep Github page and start getting familiar with it.