Home Using Loops and Conditions in Bicep
Post
Cancel

Using Loops and Conditions in Bicep

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

  1. MicrosoftLearn
  2. Youtube series: Introduction to Bicep
This post is licensed under CC BY 4.0 by the author.