for_each in HashiCorp Terraform can be used to create more advanced Terraform configurations that are able to deploy multiple instances of a resource or module with similar or slightly different configurations. In this article, we will explore how to use the for_each loop in Terraform with basic code examples. The examples are targeting resource types within the azurerm Terraform provider, but the same code can be used in Terraform regardless of provider and type needed.

Let’s get started!

What is for_each in Terraform?

for_each is a loop function in HashiCorp Terraform that allows you to create multiple instances of a resource with varying attributes. Unlike count, which creates multiple instances of a resource with identical attributes, for_each creates instances with unique attribute values. This feature allows you to define your resources more precisely and accurately.

The syntax for using for_each looks as follows:

resource <PROVIDER_TYPE> "<NAME>" {
  for_each = <COLLECTION>

  # resource config here
}

The <PROVIDER_TYPE> and <NAME> for the resource block will remain the same as with any other resource configuration is defined in Terraform code. The difference is adding the for_each and passing it a collection. The collection will be looped over with each item in the set or map being a new instance of the resource that will be configured. The collection can be coded inline or as a local or var value that is passed to it.

Within the resource configuration, Terraform will use the same configuration for every resource that is configured as it loops through the collection passed as a set or map to the for_each. When looping through the collection, the item in the collection is accessed through the each keyword. The each keyword has a key and value parameter that can be used to access the key and value of the item in the collection.

Using Terraform for_each with a set

A set is a way to define a collection of items to pass to for_each for configuring multiple resource instances. If a list is used in the code, then the toset() function can be used to convert it to a set. This may be required as lists are not supported directly when using for_each.

This method of passing a set to for_each can be used to configure both resource and module blocks in Terraform, as follows.

Use for_each on a resource block

The following is a simple example of using a set to define the items for the for_each set on a resource block. This example defines a list of Azure Resource Group names, then uses the toset() function to convert the list to a set so that for_each can be used to iterate over them and configure the resources.

resource azurerm_resource_group "resource_groups" {
  for_each = toset([
    "prod-b59-webapp1-rg",
    "prod-b59-database1-rg"
  ])

  name     = each.key
  location = "eastus"
}

This example provisions multiple Azure Resource Groups (via the azurerm_resource_group type) by passing a set of strings defining the resource group names. When doing this, there is only a value on the each object that is used to access each item in the collection. So, for this, only the each.key is used to access the string value of the items in the collection.

Use for_each on a module block

The method of passing a set to the for_each parameter can also be used to configure the usage of Terraform module blocks as well.

The following is a simple example of using a set to define the items for the for_each set on a module block. This example defines a list of names to use, converting the list of a set using the toset() function, so multiple instances of the module can be configured accordingly.

module "resource_groups" {
  for_each = toset([
    "prod-b59-webapp1",
    "prod-b59-database1"
  ])

  source = "./resource_groups_module"

  name = "${each.key}-rg"
}

The name parameter of the module is configured using the each.key to access the item value from the list. Notice, an expression is used to append a string to the key as it configures the value of the name parameter. This is just showing that an expression can optionally be used with each.key if necessary.

Using Terraform for_each with a map

A map is a way to define a collection of key-value pairs in Terraform. The keys and values of the map can be of any data type.

The following is a simple map definition of the locals.resource_groups variable with items that can be used to define Azure Resource Groups with the name as the key and the location as the value:

locals {
  resource_groups = {
    "prod-b59-webapp1-rg" = "eastus"
    "prod-b50-database1-rg" = "eastus"
  }
}

Using the previous map definition, the following is an example of using a for_each to configure Azure Resource Groups using this map:

resource azurerm_resource_group "resource_groups" {
  for_each = local.resource_groups

  name     = each.key
  location = each.value
}

In this example, for_each is being used to configure multiple Azure Resource Groups (using the azurerm_resource_group resource type) as defined by the locals.resource_groups variable.

When using for_each, the each keyword is used to access the specific item in the collection as it’s looped over. In this example, the name parameter is being set to the key of the item in the collection using each.key. The location parameter is being set to the value of the item in the collection using the each.value.

Using Terraform for_each with a map of Objects

Let’s take a look at a similar example to the previous use of a map with the for_each to define the list of resources to configure but with a map of objets this time. Instead of simple key-value pairs, we’ll use a map that has a key of type string with the value being an object that can contain one or more values itself. This can be used to set up more complex configurations using for_each.

The following is and example definition of a locals.virtual_machines variable with some configurations defined using a map of objects to declare the Azure Virtual Machines to configure.

locals {
  virtual_machines = {
    "prod-b59-vm1" = {
      location = "eastus"
      size = "Standard_B2s"
    }
    "prod-b59-vm2" = {
      location = "westus"
      size = "Standard_B4ms"
    }
  }
}

Using the previous map of objects definition, the following is an example of using a for_each to configure Azure Virtual Machines using this map:

resource azurerm_virtual_machine "vm" {
  for_each = local.virtual_machines

  name     = each.key
  location = each.value.location
  vm_size  = each.value.size
  
  # other VM configurations here
}

This example used the each.key for setting the name parameter of the azurerm_virtual_machine resource, and with the each.value to access the object it was able to access the location and size properties to set the location and vm_size parameters of the azurerm_virtual_machine resource.

Advantages of using for_each in Terraform

Using the Terraform for_each has several advantages over using other loop functions such as count:

  • Simplifies Resource Management
    With for_each, you can manage multiple resources of the same type with a single block of code. This is useful when you have a several resources to manage that are similarly configured, and it reduces the amount of code you need to write and maintain.
  • Avoids Repetitive Code
    Without using for_each, multiple resource and/or module blocks would be needed. This would result in repeating the same code multiple times, and would violate the DRY (Don’t Repeat Yourself) practice of writing clean code. With for_each, you can create multiple resources with the same or similar configuration in a single block, which can help to simplify your code.
  • Provides Granular Control
    With for_each, the creation of multiple resources with unique attributes more easily as compared to use count instead. This provides more granular control over the configuration of resources performed using the for_each loop function instead of count.
  • Increases Efficiency
    Configuring multiple resources or modules with the same or similar settings can be time-consuming and error prone due to user error. The use of for_each simplifies this process by reducing the amount of code written that would otherwise end up being copied and pasted. This helps improve quality and decrease coding time when appropriate.

Using for_each in HashiCorp Terraform provides several advantages that make it easier to manage your infrastructure. It simplifies resource management, avoids repetitive code making code more DRY, provides granular control, and increases efficiency. These benefits make for_each a valuable tool for configuring resources and modules in Terraform, especially when working with large infrastructures.

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
Microsoft MVP HashiCorp Ambassador

Discover more from Build5Nines

Subscribe now to keep reading and get access to the full archive.

Continue reading