Loops, Conditions and Conditional Loops
Stephen Tulp
December 7, 2023
7 minutes to read
Introduction
In the previous posts we went through the anatomy and structure of a Bicep file and also learnt about using an external parameter file using the new .bicepparams
extension. In this post we will go through the different ways we can use loops and conditions in Bicep to provide greater flexibility in our deployments.
Loops
You can use loops to define multiple copies of a resource, module, variable, property, or output. Use loops to avoid repeating syntax in your Bicep file and to dynamically set the number of copies to create during deployment.
To use loops to create multiple resources or modules, each instance must have a unique value for the name property. You can use the index value or unique values in arrays or collections to create the names.
Loops can be declared in a couple of ways.
Integer Index
Using an integer index. This option works when your scenario is: “I want to create this many instances.” The range function creates an array of integers that begins at the start index and contains the number of specified elements. Within the loop, you can use the integer index to modify values.
[for <index> in range(<startIndex>, <numberOfElements>): {
...
}]
Items in an Array
Using items in an array. This option works when your scenario is: “I want to create an instance for each element in an array.” Within the loop, you can use the value of the current array element to modify values.
[for <item> in <collection>: {
...
}]
Items in a Dictionary Object
Using items in a dictionary object. This option works when your scenario is: “I want to create an instance for each item in an object.” The items function converts the object to an array. Within the loop, you can use properties from the object to create values.
[for <item> in items(<object>): {
...
}]
Integer Index and Items in an Array
Using integer index and items in an array. This option works when your scenario is: “I want to create an instance for each element in an array, but I also need the current index to create another value.”
[for (<item>, <index>) in <collection>: {
...
}]
Loop Examples
The following example creates a resource group for each name provided in the commonResourceGroups
array parameter.
param location string = resourceGroup().location
param commonResourceGroups array = [
'alertsRG'
'networkWatcherRG'
]
module sharedResourceGroups '../resourceGroup/resourceGroups.bicep' = [for item in commonResourceGroups: {
name: item
scope: subscription()
params: {
resourceGroupNames: commonResourceGroups
location: location
}
}]
Conditions
To optionally deploy a resource or module in Bicep, use the if
expression. An if
expression includes a condition that resolves to true or false. When the if
condition is true, the resource is deployed. When the value is false, the resource isn’t created. The value can only be applied to the whole resource or module.
Condition Examples
The following example conditionally deploys a resource group for networking resources. When virtualNetworkEnabled
is true
, it deploys the resource group. When virtualNetworkEnabled
is false
, it skips the deployment.
param virtualNetworkEnabled bool
module resourceGroupForNetwork '../CARML/resources/resource-group/main.bicep' = if (virtualNetworkEnabled) {
name: 'resourceGroupForNetwork-${guid(deployment().name)}'
scope: subscription(subscriptionId)
params: {
name: resourceGroups.network
location: location
tags: tags
}
}
We can also use !
, which is the not
operator, this will then deploy the Subscription Tags module if the tags
parameter is not empty.
module subscriptionTags '../CARML/resources/tags/main.bicep' = if (!empty(tags)) {
scope: subscription(subscriptionId)
name: 'subTags-${guid(deployment().name)}'
params: {
subscriptionId: subscriptionId
location: location
onlyUpdate: true
tags: tags
}
}
Lastly, we can also combine both of them to give additional flexibility. Here we are deploying the Subscription Placement module if the subscriptionManagementGroupAssociationEnabled
parameter is true
and the subscriptionMgPlacement
parameter is not empty. Using &&
, which is the and
operator makes it so that both conditions need to be true. If they are both true
then it will deploy. Otherwise, it will skip the deployment.
module subscriptionPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (subscriptionManagementGroupAssociationEnabled && !empty(subscriptionMgPlacement)) {
scope: managementGroup(subscriptionMgPlacement)
name: 'subscriptionPlacement-${guid(deployment().name)}'
params: {
targetManagementGroupId: subscriptionMgPlacement
subscriptionIds: [
subscriptionId
]
}
}
Conditional Loops
Conditional Loop deployments works when your scenario is: “I want to create multiple instances, but for each instance I want to deploy only when a condition is true.”
[for <item> in <collection>: if(<condition>) {
...
}]
The example below will only deploy the role assignments if the roleAssignmentEnabled
parameter is true
and the roleAssignments
parameter is not empty. It will loop through each of the assignments (principalId, definition, principalType, relativeScope) in the RoleAssignments
array and assign it at the Subscription scope /
.
@description('Whether to create role assignments or not. If true, supply the array of role assignment objects in the parameter called `roleAssignments`.')
param roleAssignmentEnabled bool = true
@description('Supply an array of objects containing the details of the role assignments to create.')
param roleAssignments array = [
{
principalId: '2b33ff60-edf0-4216-b2a6-66ec07050fd4'
definition: 'Reader'
principalType: 'Group'
relativeScope: '/'
}
{
principalId: '20bbeee1-e70c-43d3-8c2c-b66fefa31acf'
definition: 'Owner'
principalType: 'ServicePrincipal'
relativeScope: '/'
}
]
module roleAssignment '../CARML/authorization/role-assignment/main.bicep' = [for assignment in roleAssignments: if (roleAssignmentEnabled && !empty(roleAssignments)) {
name: take('roleAssignments-${uniqueString(assignment.principalId)}', 64)
params: {
location: location
principalId: assignment.principalId
roleDefinitionIdOrName: assignment.definition
subscriptionId: subscriptionId
resourceGroupName: (contains(assignment.relativeScope, '/resourceGroups/') ? split(assignment.relativeScope, '/')[2] : '')
}
}]
Conclusion
By using conditions and loops we have a lot of flexibility and options for our deployments. We can now deploy resources and modules conditionally and also loop through arrays and collections to deploy multiple instances of resources and modules. These allow us to provide options for our deployments and also optimize our deployments by only deploying what we need.
Further Reading
Some further reading on the topics covered in this post: