Conditions and Loops
Conditions can especially usefull in multi environment deployments. We can deploy a certain resources in dev or test environment and deploy anothor in production environment. Consider the following example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@description('Set this to true, if we want to deploy a storage account')
param deployStorageAccount bool
param storageAccountName string = 'stgAccBicep${uniqueString(resourceGroup().id)}'
param location string = resourceGroup().location
@allowed([
'npe'
'prd'
])
param environmentType string
var storageAccountSkuName = (environmentType == 'npe') ? 'Standard_GRS' : 'Standard_LRS'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = if(deployStorageAccount) {
name: storageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
properties:{
accessTier: 'Hot'
}
}
There are downsides that we need to be aware of, for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = if (auditingEnabled) {
name: auditStorageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
}
resource auditingSettings 'Microsoft.Sql/servers/auditingSettings@2023-08-01-preview' = {
parent: server
name: 'default'
properties: {
state: 'Enabled'
storageEndpint: storageAccount.properties.primaryEndpoints.blob
}
}
In the above example, we will get an error suggest that storageEndpoint is not found because we are not deploying the actual storage account as it depends on environmentType. In such cases, we can leverage ?
operator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = if (auditingEnabled) {
name: auditStorageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
}
resource auditingSettings 'Microsoft.Sql/servers/auditingSettings@2023-08-01-preview' = {
parent: server
name: 'default'
properties: {
state: 'Enabled'
storageEndpint: environmentType == 'npe' ? storageAccount.properties.primaryEndpoints.blob : ''
}
}
using storageEndpint: environmentType == 'npe' ? storageAccount.properties.primaryEndpoints.blob : ''
, we have applied a condition on which storage endpoint to use. Using conditional deployments helps with the consistancy of the resources without having to remember which resource is for production and which is for development.
Multiple Resources using loops
Loops can be used to deploy similar resources multiple times, like deploy multiple vnets or multiple storage account with similar configurations. In such cases, we can leverage loops.
1
2
3
4
5
6
7
8
9
10
11
12
13
param storageAccpountNames array = [
'stgaccwestustestbicep'
'stgaccwestustestbiceppp'
'stgaccwestustestbicepp
]
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = [for storageAccountName in storageAccountNames: {
name: storageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
}]
Different types of loops with Examples
Copy Loops
The following is a simple representation on leveraging array type parameters to deploy multiple resource with same configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var storageAccountSkuName = (environmentType == 'prd') ? 'Standard_GRS' : 'Standard_LRS'
param storageAccountNames array = [
'stgaccounttestbicep'
'stgaccounttestbicepp'
'stgaccounttestbiceppp'
]
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = [ for storageAccountName in storageAccountNames: {
name: storageAccountName
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
}]
Loop based on count
range()
is an another keyword that can be used to deploy the same resource multiple times. Important note to remember is that, When using range, it is a bit different from our normal range. consider the following example, here range(1, 4)
indicates that starting from 1 we need 4 containers. Hence the below code will create containers from 1,2,3,4. If you have the range something like range(5,6)
it will create 6 storage accounts starting from the number 5
1
2
3
4
5
6
7
8
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = [ for i in range(5,6): {
name: 'storagebiceptemptest${i}'
location: location
sku: {
name: storageAccountSkuName
}
kind: 'StorageV2'
}]
Iteration Index
Leverging the iteration index of the parameter within the array can be really useful in situation of having a unique name for a resource is mandatory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
param locations array = [
'westeurope'
'eastus2'
'westus2'
]
param sqlServerAdminUsername string
@secure()
param sqlServerAdminPassword string
resource sqlServer 'Microsoft.Sql/servers@2024-05-01-preview' = [ for (location, i) in locations: {
name: 'sqlServers-${i+1}'
location: location
properties: {
administratorLogin: sqlServerAdminUsername
administratorLoginPassword: sqlServerAdminPassword
}
}]
Filter data using Loops
Loops can also be used to filter data. Considering the following example, where we are creating resources only in the non production environment but we can declare the production details and use the same template in production without making any changes to the template.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
param sqlServerAdminUsername string
@secure()
param sqlServerAdminPassword string
@description('array of properties for the sql server name, location nad environmentName')
param sqlServeDetails array = [
{
name: 'sqlserver-bicep-we'
location: 'westeurope'
environmentName: 'npe'
}
{
name: 'sqlserver-bicep-wu2'
location: 'westus2'
environmentName: 'npe'
}
{
name: 'sqlserver-bicep-eus2'
location: 'eastus2'
environmentName: 'prd'
}
]
resource sqlServerFilters 'Microsoft.Sql/servers@2024-05-01-preview' = [for sqlServer in sqlServeDetails: if (sqlServer.environmentName == 'npe') {
name: sqlServer.name
location: sqlServer.location
properties: {
administratorLogin: sqlServerAdminUsername
administratorLoginPassword: sqlServerAdminPassword
}
tags: {
environment: sqlServer.environmentName
}
}]
NOTE: SQL Servers takes a lot of time to deploy especially, if we are trying to deploy two.
Control Loop and Nested Loops
By default, ARM will deploy the resources in parallel if they are in a loop. But if we need to hava more control on what this parallel execution looks like, we can use @batchSize
this can help restrict how many individual resources can be deployed at the same time.
@batchSize()
is specific to a loop. For one loop, we can have one limit. We can have different limits of execution on different resources as long as they have different loops.
Nested Loops
Lets say, we need to create a vnet with three subnets in each virtual network. In such cased, we can leverage nested loops. The following is an example on how nested loops can be used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource virtualNetworks 'Microsoft.Network/virtualNetworks@2024-01-01' = [for (location, i) in locations : {
name: 'vnet-${location}'
location: location
properties: {
addressSpace:{
addressPrefixes:[
'10.${i}.0.0/16'
]
}
subnets: [for j in range(1, subnetCount): { // Nested Loop. i being re-used in this nested loop
name: 'subnet-${j}'
properties: {
addressPrefix: '10.${i}.${j}.0/24'
}
}]
}
}]
Variable Loops and Outputs
We can also loop variables and leverage the output from these deployments, we can create other resources dependent on these variables. Consider the following code snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
param addressPrefix string = '10.10.0.0/16'
param subnets array = [
{
name: 'frontend'
ipAddressRange: '10.10.0.0/24'
}
{
name: 'backend'
ipAddressRange: '10.10.1.0/24'
}
]
var subnetsProperty = [for subnet in subnets: {
name: subnet.name
properties: {
addressPrefix: subnet.ipAddressRange
}
}]
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' = {
name: 'teddybear'
location: resourceGroup().location
properties:{
addressSpace:{
addressPrefixes:[
addressPrefix
]
}
subnets: subnetsProperty
}
}
Here all we have to update is the name and ipAddressRange in the parameter file and we can reuse the module with any parameter file that have these parameters. This helps with modularization and distribution of code with other who doesn’t have much information about the code but just want the resources to be deployed.
We can use outputs similarly with outputs as well. Assume we have created 3 storage accounts and we need to assgin the endpoints to create some settings. In such cases, we can use leverage the outputs with loops.
References
- MicrosoftLearn
- Youtube series: Introduction to Bicep