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
Withfor_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 usingfor_each
, multipleresource
and/ormodule
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. Withfor_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
Withfor_each
, the creation of multiple resources with unique attributes more easily as compared to usecount
instead. This provides more granular control over the configuration of resources performed using thefor_each
loop function instead ofcount
. - 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 offor_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.