Bicep | Conditional Iterative Deployment

Posted by Andrew Wilson on Tuesday, April 11, 2023

Background

I have recently been looking at creating multiple of the same resource using Bicep. There is however a condition where I would wish for the set of resources not to be deployed. The following stages show my work through of this particular problem (using a storage account resource as an example):

Conditional Deployment

Conditional Deployment is used where you may or may not wish to deploy a given resource depending on the outcome of a given condition (if statement).

/**********************************
Bicep Template: Conditional Storage
***********************************/

targetScope = 'resourceGroup'

// ** Parameters **
// ****************

param name string

param location string

param deployStorage bool

// ** Variables **
// ***************

// ** Resources **
// ***************

resource storageAccountDeployment 'Microsoft.Storage/storageAccounts@2021-02-01' = if (deployStorage) {
  name: name
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Premium_LRS'
  }
}

// ** Outputs **
// *************

In the instance shown above, if the deployStorage parameter is True then the storageAccountDeployment will be deployed, if False then the storageAccountDeployment will not be deployed. As an ARM template the instance above will appear as follows:


{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "name": {
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "deployStorage": {
      "type": "bool"
    }
  },
  "resources": [
    {
      "condition": "[parameters('deployStorage')]",
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-02-01",
      "name": "[parameters('name')]",
      "location": "[parameters('location')]",
      "kind": "StorageV2",
      "sku": {
        "name": "Premium_LRS"
      }
    }
  ]
}

Looping Resources

Iterative loops in Bicep utilise the For syntax as shown below:


/**********************************
Bicep Template: Iterative Storage
***********************************/

targetScope = 'resourceGroup'

// ** Parameters **
// ****************

param location string

param storageAccounts array

// ** Variables **
// ***************

// ** Resources **
// ***************

resource storageAccountDeployment 'Microsoft.Storage/storageAccounts@2021-02-01' = [for storage in storageAccounts: {
  name: storage.name
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Premium_LRS'
  }
}]

// ** Outputs **
// *************

The syntax above will deploy a number of storage accounts based on the number of storage items in the parameter array. In ARM this appears as follows:


{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "name": {
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "storageAccounts": {
      "type": "array"
    }
  },
  "resources": [
    {
      "copy": {
        "name": "storageaccountDeployment",
        "count": "[length(parameters('storageAccounts'))]"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-02-01",
      "name": "[parameters('name')]",
      "location": "[parameters('location')]",
      "kind": "StorageV2",
      "sku": {
        "name": "Premium_LRS"
      }
    }
  ]
}

Conditional Looping Resources

Iterative Conditional Deployments are used in the instance where you would like to create multiple instances of a resource, but on each iteration you would like to check if the resource should be deployed based on a condition. Such as:


/********************************************
Bicep Template: Iterative Conditional Storage
*********************************************/

targetScope = 'resourceGroup'

// ** Parameters **
// ****************

param location string

param storageAccounts array = [
  {
    name: 'st1'
    kind: 'StorageV2'
  }
]

// ** Variables **
// ***************

// ** Resources **
// ***************

resource storageAccountDeployment 'Microsoft.Storage/storageAccounts@2021-02-01' = [for storage in storageAccounts: if(storage.kind != 'BlobStorage') {
  name: storage.name
  location: location
  kind: storage.kind
  sku: {
    name: 'Premium_LRS'
  }
}]

// ** Outputs **
// *************

The example above will loop through each storage item in the storageAccounts array. At each point of iteration, the conditional is evaluated. In this example only deployment of an item will occur if it is not a 'BlobStorage' account type. The ARM for this example appears as follows:


{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string"
    },
    "storageAccounts": {
      "type": "array",
      "defaultValue": [
        {
          "name": "st1",
          "kind": "StorageV2"
        }
      ]
    }
  },
  "resources": [
    {
      "copy": {
        "name": "storageAccountDeployment",
        "count": "[length(parameters('storageAccounts'))]"
      },
      "condition": "[not(equals(parameters('storageAccounts')[copyIndex()].kind, 'BlobStorage'))]",
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-02-01",
      "name": "[parameters('storageAccounts')[copyIndex()].name]",
      "location": "[parameters('location')]",
      "kind": "[parameters('storageAccounts')[copyIndex()].kind]",
      "sku": {
        "name": "Premium_LRS"
      }
    }
  ]
}

Problem Space

The previous case conditionally deploys each resource in the looped array. Although this appears to be what we are looking for, it’s not. We would like to conditionally loop through the array which is a step higher than what is provided to us.

At this point, we are not limited by the Bicep language rather that ARM conditionals and loops do not support this behaviour.

Solution | Conditional Module with Looping Resource

To solve this problem, we need to rely on Bicep Modules or in ARM this is nested resource deployments. Essentially we need to split the problem into two. The calling template has the conditional module whilst the nested template (module) handles the looping of storage account creation. For example:

Calling Template:


/********************************************
Bicep Template: Conditional Storage Module
*********************************************/

targetScope = 'resourceGroup'

// ** Parameters **
// ****************

param location string

param StorageDeploy bool

param storageAccounts array = [
  {
    name: 'st1'
    kind: 'StorageV2'
  }
]

// ** Variables **
// ***************

// ** Resources **
// ***************

module storageDeploy 'storageDeploy.bicep' = if (StorageDeploy) {
  name: 'storageDeploy'
  params:{
    location: location
    storageAccounts: storageAccounts
  }
}

// ** Outputs **
// *************

Nested Template (Module)


/*********************************
Bicep Template: Iterative Storage
**********************************/

targetScope = 'resourceGroup'

// ** Parameters **
// ****************

param location string = resourceGroup().location

param storageAccounts array

// ** Variables **
// ***************

// ** Resources **
// ***************

resource storageAccountDeployment 'Microsoft.Storage/storageAccounts@2021-02-01' = [for storage in storageAccounts: {
  name: storage.name
  location: location
  kind: storage.kind
  sku: {
    name: 'Premium_LRS'
  }
}]

// ** Outputs **
// *************

ARM representation


{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string"
    },
    "StorageDeploy": {
      "type": "bool"
    },
    "storageAccounts": {
      "type": "array",
      "defaultValue": [
        {
          "name": "st1",
          "kind": "StorageV2"
        }
      ]
    }
  },
  "resources": [
    {
      "condition": "[parameters('StorageDeploy')]",
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2022-09-01",
      "name": "storageDeploy",
      "properties": {
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "mode": "Incremental",
        "parameters": {
          "location": {
            "value": "[parameters('location')]"
          },
          "storageAccounts": {
            "value": "[parameters('storageAccounts')]"
          }
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {
            "location": {
              "type": "string",
              "defaultValue": "[resourceGroup().location]"
            },
            "storageAccounts": {
              "type": "array"
            }
          },
          "resources": [
            {
              "copy": {
                "name": "storageAccountDeployment",
                "count": "[length(parameters('storageAccounts'))]"
              },
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2021-02-01",
              "name": "[parameters('storageAccounts')[copyIndex()].name]",
              "location": "[parameters('location')]",
              "kind": "[parameters('storageAccounts')[copyIndex()].kind]",
              "sku": {
                "name": "Premium_LRS"
              }
            }
          ]
        }
      }
    }
  ]
}

We have now constructed a template (or two) which will only conduct looping of the resource deployment if we have said so through our StorageDeploy module conditional parameter.