This post continues the series on the topic of Infrastructure as Code (IaC) with Azure Bicep, an extension of the ARM templates. After we had highlighted a few general IaC aspects with classification of Bicep in the first parts and were able to start a first deployment with the setup of the local development environment, we are now going one step further.
As with other IaC and programming languages, tools for dynamizing the code are essential to make it more readable, flexible, and maintainable. In our context, this not only makes it easier to edit the templates, but also enables the sensible automation and scaling of infrastructure creation with Bicep.
We will take a closer look at the options with conditions and loops in Bicep with practical examples. In addition, the benefits of Azure’s in-house as well as user-defined functions and types in the application will be demonstrated.
As the series progresses, we’ll look at other topics such as modularization and DevOps.
For this concrete post, let´s check looping and conditions in Bicep.
Conditions and Loops
Conditional Values and Executions
Unlike Terraform, Azure Bicep directly supports the if keyword for loops as well as resource and module blocks. It has the same purpose as in other languages – it allows conditional execution of code based on a specific condition. This is especially useful when resources are only to be created or configured in certain circumstances, such as only in selected environments. Bicep therefore does not need the workaround about the number of resources to be created like count in Terraform.
The general syntax for the if keyword in Bicep is as follows:
Resource = if (condition=true) { configuration of the resource} else {no provisioning}
The condition can be a predefined Bool value or a direct evaluation. As a simple example of a storage account, this would be:
resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = if (deployStorage) {
name: uniqueStorageName
}
As a result, the storage is only deployed if the condition is true.
A construct that is also very frequently used in Bicep is the ternary operator, the shortened if query. Typically, this logic sets a variable value – for example, the currently used bool or simply a conditionally determined resource name. The syntax should be familiar:
Variable = Condition ? TrueValue : FalseValue
Another nesting is also possible. Depending on your needs, the operator can also be used directly at the required point in the configuration. A simple idea at this point would be to create the storage with a higher SKU variant and thus better redundancy in relation to the target environment:
var stoSKU = env == 'development' ? 'Standard_LRS' : env == 'testing' ? 'Standard_GRS' : 'Standard_RAGZRS'
resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = if (deployStorage) {
name: uniqueStorageName
location: location
sku: {
name: stoSKU
}
…
}
Loops
Loops in Azure Bicep , as in other programming languages, are a powerful construct used to automate repetitive tasks. They are especially useful when creating multiple resources with similar properties. Loops can be used to create a certain number of resources without having to define each resource individually.
There are basically two types of loops in Bicep: countable and conditional loops. Countable loops are index-based – the classic for – and are used to create a resource with a certain number, while conditional loops (for-each) are used to create a resource with respect to each item in a collection. However, the division refers more to the way of application and logic, per syntax there is only the for keyword. In many cases, both variants would come into question, so the choice is partly a matter of taste.
To illustrate how it works, let’s take Blob Container Resources at this point, which can be created very quickly and is therefore a good idea.
Assuming a storage account with a deployed blob service, a certain number of containers can be created via the for loop:
resource blobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = [for i in range(1, 3): {
Name: 'container${i}'
parent: blobServices
properties: {
publicAccess: 'None'
}
}]
The index name can be freely chosen and reused within the resource. The range function defines the set through which the index iterates. Further dynamics could be introduced with variables instead of a fixed index space. The parent specification in the code example refers to the affiliation to the corresponding blob service and indirectly to the storage account – more on this in the next posts.
For-each is useful if you simply want to iterate through a list given as a parameter, for example. This allows a deployment to be fed with the desired names and the number of resources at the same time, dynamically from none at all to the desired quantity. The following example creates a container directly for each entry in the array:
var containerNames = ['for1container', 'for2container', 'for3container']
resource blobContainersFor 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = [for containerName in containerNames: {
name: containerName
parent: blobServices
properties: {
publicAccess: 'None'
}
}]
A combination of both variants with index and name of the collection also works:
resource blobContainersForIndex 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = [for (containerName, i) in containerNames: {
name: '${containerName}indexequals${i*100}'
parent: blobServices
properties: {
publicAccess: 'None'
}
}]
Within the resource, both the index value of the iteration and the value from the collection can now be accessed. String interpolation and expressions are also supported.
To conclude the interesting possibilities, another combination should be briefly presented here: The linking of loop and if. The following example would simply create a container from the collection:
resource blobContainersForIf 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = [for containerName in containerNames: if (containerName == 'for1container') {
name: '${containerName}ifinloop'
parent: blobServices
properties: {
publicAccess: 'None'
}
}]
It can also be helpful to have variable values created dynamically, e.g. index-based. The following statement generates an array with 10 entries as a variable:
var genContainer = [for i in range(1, 10): 'container${i}']
Further details can be found here[1].
With this post, we covered all the basics we need to use conditions and loops for Bicep development. Next, we will have a look at functions.
[1] https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/loops
