Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions

In this post, we will discuss child resources in Bicep, how to define them and their relationship to the parent resource, how to work with these resources in the presence of loops and conditional deployments, and also look at some examples and more advanced use cases.

A child resource is a resource which exists only in the context of another resource and cannot exist without it. Child resources are sometimes called nested resources, nesting of resources can arbitrarily deep as long as it is supported by the schema.

Most likely, you’ve already encountered such resources in Azure. Let’s take a look at some common examples:

So, as we can see, child resources are widely used in Azure, and that’s why it is useful to know how to manage them using Bicep and Infrastructure As Code mindset.

Contents:

Overview

The first section 3 Ways To Declare Child Resources covers variations on how to specify child resources in a Bicep file within/without and with/without parent resource definition.

If we choose to define resource within its parent, then Accessing Nested Resource With :: Operator comes in handy when we want to get some properties of the nested resource by its symbolic name.

Sometimes the decision whether to deploy a particular resource is based on some condition. This condition functionality has slightly different behavior depending on the way a child resource is declared, this is covered in Conditional Deployment section.

Loops: Creating Multiple Resources describes how loops can be used in a combination with the approaches to declare child resource discussed in the beginning of the post.

Example: Storage Account - Putting It All Together wraps up the post with an imaginary deployment of a storage account with containers where most of the concepts discussed throughout the post are applied.

3 Ways To Declare Child Resources

Obviously, when declaring a child resources, we need to provide information about its parent resource in some way.

Bicep provides multiple ways to declare child resources, all of them being quite handy and easy to use.

Within Parent Resource

One of the ways to define child resources is to declare them inside of a parent resource as shown in the code snippet below.

NOTES:

As a general rule, a child resource declared within its parent can access parent resource properties and siblings. At the same time, parent resource cannot access its child resources because it would lead to cyclic dependencies.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
param keyVaultName string = 'kv-contoso'
@secure()
param adminPassword string

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: keyVaultName
  // Other Key Vault properties are ommitted

  // Type could also contain version, e.g. 'secrets@2018-02-14'
  resource adminPwd 'secrets' = {
    name: 'admin-password'
    properties: {
      value: adminPassword
    }
  }

  // Additional child resources
  // resource userPwd 'secrets' = {
  //   name: 'user-password'
  //   ...
  // }
}

Outside Parent With “parent” Property

Sometimes we want to be able to declare child resources separately, not within a parent resource.

For this case Bicep has parent property where symbolic name of the parent resource can be passed. Basically, this allows Bicep to automatically infer name of the parent without us specifying multiple segments.

NOTES:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
param keyVaultName string = 'kv-contoso'
@secure()
param adminPassword string

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: keyVaultName
  // Other Key Vault properties are ommitted
}

// Full type with version
resource adminPwd 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: 'admin-password'   // Only one segment
  parent: kv               // Link to parent resource
  properties: {
    value: adminPassword
  }
}

Outside Parent Without “parent” Property

Last but not least, a child resource can be declared in a standalone manner like it can be done in an ARM template.

NOTES:

1
2
3
4
5
6
7
8
9
10
11
param keyVaultName string = 'kv-contoso'
@secure()
param adminPassword string

// Full type with version
resource adminPwd 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: '${keyVaultName}/admin-password'  // Two segments
  properties: {
    value: adminPassword
  }
}

Accessing Nested Resource With :: Operator

Operator :: allows accessing a child resource declared within its parent resource by using symbolic names of the parent and child.

The problem is that the child resource symbolic name cannot be used directly in the rest of the template because this symbolic name only exists within the scope of a parent resource.

Let’s take a look at an example of Key Vault and a secret, note how :: operator is used to get id of the secret using symbolic names.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Referencing existing Key Vault
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: 'kv-contoso'

  resource adminPwd 'secrets' = {
    name: 'admin-password'
    properties: {
      value: 'somesecretvalue'
    }
  }
}

// Operator :: is used to access child resource
output secretId string = kv::adminPwd.id

Conditional Deployment

Conditional deployment allows deciding whether a resource should be deployed based on a condition evaluated at the start of the deployment.

For example, we can introduce a parameter which controls whether our resource will be deployed, or we can compute the condition based on the state of other resources.

Just keep in mind a couple of things when using conditional deployment for a child resource or its parent:

So, the second use case requires attention because if a condition is only applied to the parent but not child, then a runtime error may occur since we try to deploy the child resource while the parent doesn’t exist.

As always, let’s illustrate the text with some code for better understanding.

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 keyVaultName string = 'kv-contoso'
param shouldDeploy bool = true

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = if (shouldDeploy) {
  name: keyVaultName
  // Other Key Vault properties are ommitted

  // Within Parent Resource
  // Condition "shouldDeploy" will be applied
  resource secret1 'secrets' = {
    name: 'secret1'
    properties: {
      value: 'somesecretvalue1'
    }
  }
}

// Outside Parent With "parent" Property
// Condition "shouldDeploy" won't be applied
resource secret2 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: 'secret2'
  parent: kv
  properties: {
    value: 'somesecretvalue2'
  }
}

// Outside Parent Without "parent" Property
// Condition "shouldDeploy" won't be applied
resource secret3 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: '${keyVaultName}/secret3'
  properties: {
    value: 'somesecretvalue3'
  }
}

Loops: Creating Multiple Resources

Loops are a very powerful functionality which allows creating multiple instances of a resource. We will cover different combinations of loops with parent and child resources.

Single Parent & Multiple Child Resources

The first use case will be simple, assume there is one Key Vault, and we want to create multiple secrets in it.

NOTE: Any of the 3 Ways To Declare Child Resources can be used, the following example declares child resources within its parent.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Creating 1 Key Vault (parent)
resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: 'kv-contoso'
  // Other Key Vault properties are ommitted

  // Creating 3 secrets (children)
  resource secrets 'secrets' = [for i in range(0,3): {
    name: 'secret${i}'
    properties: {
      value: 'somesecretvalue${i}'
    }
  }]
}

Multiple Parent & One Child Resource Per Parent

Slightly more complicated case is when we want to create multiple Key Vaults, each having one secret.

NOTE: Nested resource cannot appear inside of for expression. In other words, when parent resource is declared using a loop, then a child resource cannot be declared within parent.

To create vaults and secrets with 1 to 1 relationship, child resources should be declared outside parent like in the example below. Note that both ways with and without parent property would work.

Another even better option is to use modules, they will be covered in the next section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
param keyVaultCount int = 2    // Number of Key Vaults to create

resource kvs 'Microsoft.KeyVault/vaults@2019-09-01' = [for i in range(0, keyVaultCount): {
  name: 'kv-contoso-${i}'
  // Other Key Vault properties are ommitted
}]

resource secrets 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [for i in range(0, keyVaultCount): {
  name: 'secret${i}'
  parent: kvs[i]    // kvs is an array of resources, getting i-th element
  properties: {
    value: 'somesecretvalue${i}'
  }
}]

Multiple Parent & Multiple Child Resources Per Parent

Let’s say we want to have multiple Key Vaults, each having multiple secrets. This is not easily achievable with the approach we used above - nested loops are not supported.

This is where modules come in very handy, they allow writing elegant and easy to understand Bicep code.

In the resulting ARM template module will compile into nested deployments where each nested deployment deploys one vault and multiple secrets.

1
2
3
4
5
6
7
8
9
// ========== main.bicep file ==========

// Creating 2 Key Vaults using module
module kvs './keyvault.bicep' = [for i in range(0,2): {
  name: 'keyvault${i}'
  params: {
    keyVaultName: 'kv-contoso-${i}'
  }
}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ========== ./keyvault.bicep file ==========

param keyVaultName string

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: keyVaultName
  // Other Key Vault properties are ommitted

  // Creating 3 secrets in Key Vault
  resource secrets 'secrets' = [for i in range(0,3): {
    name: 'secret${i}'
    properties: {
      value: 'somesecretvalue${i}'
    }
  }]
}

Example: Storage Account - Putting It All Together

In the previous sections, a lot of things were covered. In all preceding examples we used Key Vault and secrets to illustrate parent-child relationship between resources.

Now, let’s take a storage account with containers and apply the following things in one example:

The code below illustrates all of the points mentioned above, also see inline comments.

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
param storageAccountName string = 'stcontoso'
param shouldCreateContainers bool = true
param containerNames array = [
  'logs'
  'data'
  'backup'
]

resource stg 'Microsoft.Storage/storageAccounts@2021-02-01' = {
  name: storageAccountName
  kind: 'StorageV2'
  location: resourceGroup().location
  sku: {
    name: 'Standard_LRS'
  }

  // Containers live inside of a blob service
  resource blobSvc 'blobServices' = {
    name: 'default'   // Always has value 'default'

    // Creating containers with provided names if contition is true
    resource containers 'containers' = [for name in containerNames: if(shouldCreateContainers) {
      name: name
      properties: {
        publicAccess: 'Blob'    // Just setting some property
      }
    }]
  }
}

// Checking if containers were created using ternary operator
// If yes then returning publicAccess property of the first container, else empty string
output containerPublicAccess string = shouldCreateContainers ? stg::blobSvc::containers[0].properties.publicAccess : ''