Nested Loops In Azure Bicep - 4 Use Cases, For-Loop, Solutions & Workarounds
Loops are one of the fundamental programming constructs which are common in many programming languages. However, Azure Bicep loops are a bit limited, and there could be some challenges in introducing nested loops in particular. In this post, we will see such use cases and how to handle them.
In Azure Bicep there is only for
-loop which can be used with resources, modules, variables, and outputs. For example, using for-loop allows defining multiple resources in the same resource declaration which removes code duplication and provides more flexibility.
Another common use case for (nested) loops is to pass complex configuration as a parameter and apply this config in the main template using loops and other constructs. This way, to add a new resource/instance, we only need to modify our parameter value without changing the main template.
Throughout this post we are going to use Azure Traffic Manager resource in the code snippets because it is a good example where we can apply nested loops. In addition to that, traffic manager profile’s ARM template Microsoft.Network/trafficManagerProfiles is relatively small and easy to understand.
Contents:
- Overview
- 1. Resource Loop [] → Nested Loop []
- 2. Resource → Property Loop [] → Nested Loop []
- 3. Resource Loop [] → Property Loop [] → Nested Loop []
- 4. Resource → Children Loop [] → Nested Loop []
- Related Posts
- Useful Links
Overview
In this post we will discuss different use cases when one might need to use a nested loop.
To make it easier to understand, we are going to use traffic manager profile in our examples because it has endpoints
property which is an array, and each endpoint object expects a customHeaders
array property:
Microsoft.Network/trafficManagerProfiles → endpoints → customHeaders
Next sections cover four use cases where nested loops come in handy and how to apply them or work around the limitation:
- Resource Loop [] → Nested Loop [] - very common use case, easy to implement
resource trafficManagerProfiles[] → property endpoints[]
- Resource → Property Loop [] → Nested Loop [] - currently, there is a limitation in Bicep and ARM template that does not allow nested loop like this one, but we discuss two options how to work around it!
resource trafficManagerProfile → property endpoints[] → property customHeaders[]
- Resource Loop [] → Property Loop [] → Nested Loop [] - this pattern becomes straightforward once we know how to implement use case #2.
resource trafficManagerProfiles[] → property endpoints[] → property customHeaders[]
- Resource → Children Loop [] → Nested Loop [] - this section discusses how to reduce this pattern to the use case #1 by leveraging the fact that these are child resources.
1. Resource Loop [] → Nested Loop []
Example: resource trafficManagerProfiles[] → property endpoints[]
This is the basic case when we want to declare multiple resources in a for
-loop and each resource instance has an array property. For example, multiple traffic managers each with multiple endpoints.
- Line 2: Configuration of traffic managers is passed as a parameter
- Line 28: Resource loop where traffic manager profiles are created
- Line 35: Property
endpoints
accepts an array of objects which is in turn created using for-loop
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
36
37
38
39
40
41
42
43
44
45
// Config of two traffic managers with endpoints
param trafficManagerConfigs array = [
{
name: 'contoso1'
endpoints: [
{
name: 'endpointA'
target: 'aaa.ochzhen.com'
}
{
name: 'endpointB'
target: 'bbb.ochzhen.com'
}
]
}
{
name: 'contoso2'
endpoints: [
{
name: 'endpointC'
target: 'ccc.ochzhen.com'
}
]
}
]
// Resource loop
resource trafficManagerProfiles 'Microsoft.Network/trafficManagerProfiles@2018-08-01' = [for trafficManagerConfig in trafficManagerConfigs: {
name: trafficManagerConfig.name
location: 'global'
properties: {
trafficRoutingMethod: 'Weighted'
...
// Property loop
endpoints: [for endpoint in trafficManagerConfig.endpoints: {
type: 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
id: '${resourceId('Microsoft.Network/trafficManagerProfiles', trafficManagerConfig.name)}/externalEndpoints/${endpoint.name}'
name: endpoint.name
properties: {
endpointStatus: 'Enabled'
target: endpoint.target
}
}]
}
}]
2. Resource → Property Loop [] → Nested Loop []
Example: resource trafficManagerProfile → property endpoints[] → property customHeaders[]
Here things become more complicated. While having a nested loop inside of resource loop is fine, having nested loop inside of a property loop is not supported as of October 2021 (see github issue: Is there plans to support nested loop on resources?).
In this case, there are a few ways how to approach this problem. Note that customHeaders
property is the one that causes problems for us since we cannot define a nested loop for it.
[Option 1 (easier)] Avoid Nested Loop By Passing Complete Array Property
Since there is no Azure Bicep or ARM template support for nested loops inside of a property, the easiest thing we can do is to avoid the need for the nested loop.
A simple way to eliminate nested loop is by passing the valid array which is of the needed format. This will allow to eliminate the mapping which nested loop does.
In the case of traffic manager endpoints, this means that customHeaders
is passed as is from the parameter. As always, it’s better to see an example, pay attention to the use of customHeaders
array on lines 10 and 64.
NOTE: This works well on the arrays whose elements are quite small, for example, customHeaders
array consists of objects with only two properties: name
and value
. Now imagine that each element has 10 properties many of which are static, this would lead to a lot of duplication (see [Option 2 (harder)] Use Module To Implement Nested Loop for an alternative solution).
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// ========== trafficmanager.bicep ==========
param trafficManagerConfigs array = [
{
name: 'contoso1'
endpoints: [
{
name: 'endpointA'
target: 'aaa.ochzhen.com'
customHeaders: [
{
name: 'header1'
value: 'value1'
}
{
name: 'header2'
value: 'value2'
}
]
}
{
name: 'endpointB'
target: 'bbb.ochzhen.com'
customHeaders: [
{
name: 'header3'
value: 'value3'
}
]
}
]
}
{
name: 'contoso2'
endpoints: [
{
name: 'endpointC'
target: 'ccc.ochzhen.com'
customHeaders: [
{
name: 'header4'
value: 'value4'
}
]
}
]
}
]
resource trafficManagerProfiles 'Microsoft.Network/trafficManagerProfiles@2018-08-01' = [for trafficManagerConfig in trafficManagerConfigs: {
name: trafficManagerConfig.name
location: 'global'
properties: {
trafficRoutingMethod: 'Weighted'
...
endpoints: [for endpoint in trafficManagerConfig.endpoints: {
type: 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
id: '${resourceId('Microsoft.Network/trafficManagerProfiles', trafficManagerConfig.name)}/externalEndpoints/${endpoint.name}'
name: endpoint.name
properties: {
endpointStatus: 'Enabled'
target: endpoint.target
// The customHeaders array in the config has the same format as property expects as input
customHeaders: endpoint.customHeaders
}
}]
}
}]
[Option 2 (harder)] Use Module To Implement Nested Loop
Modules in Azure Bicep are a very useful construct which can help solve the problem with a nested loop, read more about modules in Learn Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes.
NOTE: The format of the customHeaders
array in config is changed from {"name": "", "value": ""}
to {"key": "", "val": ""}
. This is just to illustrate the need for mapping from one format to another so that the problem cannot be solved using [Option 1 (easier)] Avoid Nested Loop By Passing Complete Array Property.
The idea is to perform customHeaders
mapping inside of a module invocation:
- There are two bicep files:
trafficmanager.bicep
andmapped-headers.bicep
- On line 33 in
trafficmanager.bicep
for eachendpoint
object we invoke a helper modulemapped-headers.bicep
which converts headers from config (key
&val
) to the desired format (name
&value
) - In the traffic manager profile declaration on line 53, we just pass mapped headers for an endpoint using the output of the corresponding module, note how index is used for that
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ========== trafficmanager.bicep ==========
param trafficManagerConfig object = {
name: 'contoso1'
endpoints: [
{
name: 'endpointA'
target: 'aaa.ochzhen.com'
customHeaders: [
{
key: 'header1'
val: 'value1'
}
{
key: 'header2'
val: 'value2'
}
]
}
{
name: 'endpointB'
target: 'bbb.ochzhen.com'
customHeaders: [
{
key: 'header3'
val: 'value3'
}
]
}
]
}
module mappedHeadersForEndpoints 'mapped-headers.bicep' = [for endpoint in trafficManagerConfig.endpoints: {
name: endpoint.name
params: {
customHeaders: endpoint.customHeaders
}
}]
resource trafficManagerProfiles 'Microsoft.Network/trafficManagerProfiles@2018-08-01' = {
name: trafficManagerConfig.name
location: 'global'
properties: {
trafficRoutingMethod: 'Weighted'
...
endpoints: [for (endpoint, idx) in trafficManagerConfig.endpoints: {
type: 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
id: '${resourceId('Microsoft.Network/trafficManagerProfiles', trafficManagerConfig.name)}/externalEndpoints/${endpoint.name}'
name: endpoint.name
properties: {
endpointStatus: 'Enabled'
target: endpoint.target
customHeaders: mappedHeadersForEndpoints[idx].outputs.mappedHeaders
}
}]
}
}
The helper module simply performs mapping and returns the result as an output.
1
2
3
4
5
6
7
8
9
10
// ========== mapped-headers.bicep ==========
param customHeaders array
var mappedHeaders = [for header in customHeaders: {
name: header.key
value: header.val
}]
output mappedHeaders array = mappedHeaders
3. Resource Loop [] → Property Loop [] → Nested Loop []
Example: resource trafficManagerProfiles[] → property endpoints[] → property customHeaders[]
Now, let’s take the solution for Resource → Property Loop → Nested Loop and extend it to work with multiple resources at once.
Luckily, this is extremely simply to achieve. Remember that we have trafficmanager.bicep
template from the previous section (either from Option 1 or Option 2).
Let’s add a new file named main.bicep
which accepts an array of traffic manager configurations and for each item invokes trafficmanager.bicep
. This is all we need to do, here are a few notes:
- Passing an array of traffic manager configs on line 3, objects are in the same format as in the previous section depending on which option we choose
- For each object we invoke a module, please refer to the previous section as well to see the contents of
trafficmanager.bicep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ========== main.bicep ==========
param trafficManagerConfigs array = [
{
name: 'contoso1'
endpoints: [
...
]
}
{
name: 'contoso2'
endpoints: [
...
]
}
]
module trafficManagers 'trafficmanager.bicep' = [for trafficManagerConfig in trafficManagerConfigs: {
name: trafficManagerConfig.name
params: {
trafficManagerConfig: trafficManagerConfig
}
}]
4. Resource → Children Loop [] → Nested Loop []
As we have already seen in the previous sections, having a nested loop (on customHeaders
) inside of a property loop (on endpoints
) is not currently supported by ARM templates and Bicep. And as a result, we had to do some workarounds.
However, if we are working with an array of child resources, then the solution is much simpler. The reason is that child resources can be declared outside of its parent resource.
So, the problem actually is getting simplified to the Resource Loop → Nested Loop case where we first deploy a parentResource
and then an array of child childResources[]
.
resource parentResource
resource childResources[] → property nestedArray[]
Read more about Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions.
Related Posts
- Parameters In Azure Bicep - Ultimate Guide With Examples
- Using Key Vault Secrets As Secure Parameters In Azure Bicep - Template & Module Inputs
- Variables In Azure Bicep - From Basics To Advanced
- Reference New Or Existing Resource In Azure Bicep
- Create Resource Group With Azure Bicep and Deploy Resources In It
- 5 Ways To Deploy Bicep File With Parameters - Azure DevOps, PowerShell, CLI, Portal, Cloud Shell
- Deploy Azure Bicep In YAML and Classic Release Pipelines (CI/CD) - Azure DevOps
Useful Links
- Learn Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes
- Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions
- bicep/loops.ms | GitHub
- GitHub Issue: Is there plans to support nested loop on resources?
- Microsoft.Network/trafficManagerProfiles - template reference | Microsoft Docs