Use For_Each in a Terraform Module with Azure VNets and Bastion Host

You have laded on another post and video in a series on Terraform and Azure.  This post and accompanying video looks at a more complicated module example that uses a looping function to create multiple, similar resources.  The example uses Terraform to create an Azure Bastion Host and VNet with multiple subnets in Azure. 

Check out other posts and videos in the series located here:
Getting Started with Terraform and Azure
Terraform Workflow with Azure
Input Variables with Terraform and Azure
Modules and Outputs with Terraform and Azure

The code used below can be found on GitHub here:
https://github.com/tsrob50/TerraformExamples/tree/main/VNetandBastionHost

For_Each

The goal with creating this module is to make it reusable for different projects. For example, one project may only need one subnet, while another may need several.  Each subnet in a VNet is similar to the others, except for the subnet name and IP Address Prefix. 

A “for_each” block creates multiple Subnets on the VNet.  We provide a map of subnet names and IP address prefixes, and for each set, the for_each argument in the resource block creates a subnet.

Map vs. List

A map in Terraform is a list of key, value pairs.  For example:

subnet_1 = {
  name             = "subnet_1"
  address_prefixes = ["10.13.1.0/24"]
}

Subnet_1 in the above example contains both the subnet name and the IP address prefix.

A list in Terraform contains a sequence of values, such as:

[“red”, ”green”, ”blue”]

We used a map for the subnets in the example that follows.

The Module

We went over the Terraform workflow, and created modules in other videos, links to those are at the top of this post.  For the following, we are going to skip to the relevant information.  Below is the resource block for the subnets.  Notice the resource group name and virtual network name use a variable and resource block.  These are the same for each subnet.  The subnet name and address prefix will be different.

Subnet Resource

We need to add the values for the name and prefix. But, first, let’s create a variable block for the subnets.

Subnet Variable

We added three subnets as default values to the variable for the example below.  The example is a map of key, value pairs that include the name and prefix for each subnet. 

Subnet Variable

The image above is missing one rather important subnet, the Azure Bastion Host subnet.  An Azure Bastion subnet must have the name “AzureBastionSubnet” and have an address space of /26 or larger.  Azure Bastion manages the Network Security Groups and other settings on the subnet and because of that, the subnet is dedicated to Azure Bastion.  Let’s add that as the Bastion Host subnet in the subnets variable.

Bastion Subnet

Looping a Resource

Now that we have defaults set for the variable, we can finish the resource block for the subnets.  We start by adding for_each as the first argument in the resource block.  The for_each statement needs a value, what value to use for the resource.  In this example, the loop will use the variable var.subnets, looping through each value in the variable.

For_Each Argument

Next, we need to provide the values for name and address_prefixes.  The loop will use each instance of those values to build the four subnets.  We can call these values with each.value followed by the name of the value in square brackets.  For example, below is what is entered to add the network name.

name  = each.value[“name”]

once finished, the resource block for the subnets will look like the image below.

For_Each Subnet Resource Block

The deployment will loop through each subnet and apply the name and address prefix for that subnet. 

Looping Output

Now that we can create multiple subnets, we may want to output values from those subnets.  For this example, we will output the subnet ID for each subnet.

From the ouputs.tf file, create a new output block called azure_subent_id, and start by adding a value.

Subnet Output

Next, we add an expression.  We can’t simply reference the subnet resource block because there are four different items in that block. Instead, we create an expression that loops through each item, outputting the subnet ID.  We will use a for expression to output the data.

## The brackets determine the data type in a for expression.  The squiggly bracket indicates an object/map.  Objects also require a separator “=>” between the key, value pair.  Square brackets return a tuple/list data type.

Start with the for statement followed by a placeholder for the given iteration of the loop.  Then, provide the value to loop.

{ for n in keys(var.map) }

After that, add a colon

{ for n in keys(var.map) : }

Next, provide the key and the value to return for that key.  Use the “=>” as a separator for the key, value pair.  Use the square bracket at the end to indicate the key of the data to be returned.

{ for n in keys(var.map) : n => resouce.name[n]}

The final output for the subnet ID’s in this example looks like below:

Subnet For Expression

This statement loops through each key in var.subnet and assigns it to id.  After that, the id and the subnet Id for each [id] is returned.

Below are the results once the module is completed and applied.

Module Output

And that, my friends, is how to use for_each with Terraform.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.