How to Use Terraform For_Each Meta-Argument

In various scenarios, the need arises to generate multiple resources with analogous configurations. Simply duplicating Terraform resource blocks with slight adjustments for each instance is not an optimal approach. This not only hampers readability but also needlessly extends the Infrastructure as Code (IaC) configuration files.

This article delves into Terraform’s for_each meta arguments, providing a solution to the aforementioned challenge. Furthermore, we will examine additional benefits, explore various use cases, and illustrate these concepts through relevant examples.

What is Terraform for_each?

Terraform’s for_each is a meta argument designed to facilitate the creation of multiple instances of a defined resource. This feature not only allows the generation of numerous replicas but also grants the flexibility to dynamically configure attributes for each instance. The configuration of these attributes depends on the variables used, providing a versatile way to replicate real-world scenarios.

for_each is particularly effective when working with sets of strings (set(string)) and maps of strings (map(string)). These string values play a crucial role in tailoring specific attributes for each resource instance.

Consider the case of creating multiple subnets – using the for_each meta-argument, distinct CIDR ranges can be specified for each subnet within the same resource block.

When for_each is employed in a resource block, a special object known as each becomes automatically available. This each object serves as a reference to each instance created by the for_each. It allows easy access to the values provided in a set and the key-value pairs offered in a map-type variable, streamlining the configuration process.

How to use for each in Terraform

A general syntax of using for_each meta argument is expressed below.

resource "<resource type>" "<resource name>" {
  for_each = var.instances
  // Other attributes

  tags = {
    Name = each.<value/key>
  }
}

The <resource type> in Terraform, exemplified by “aws_vpc,” denotes the specific type of Terraform resource being utilized.

The <resource name> serves as a user-defined identifier for this resource, facilitating references elsewhere within the Terraform configuration. The for_each attribute assumes a variable value, typically in the form of “var.instances.” The variable “var.instances” can be either of list or map type.

The number of resources generated for the specified “<resource type>” is contingent upon the length of the set or map assigned to “var.instances.”

Each resource instance created by this process is given a Name tag through the utilization of the each object. If “var.instances” is of set type, the only available property is “each.value.” Conversely, in the case of a map, both the key and value can be accessed using “each.key” and “each.value.”

In more advanced scenarios, it is feasible to employ a map of objects (map(object)) with the for_each meta-argument. This will be elaborated upon later in this post.

It’s important to note that the terminal outputs showcased in this blog post exclusively present the output of the “terraform plan” command to demonstrate the application of for_each. Readers are encouraged to execute the “apply” commands themselves for a more hands-on experience.

Using for_each with set of strings

Consider a scenario where a specific quantity of EC2 instances needs to be generated in AWS. The determination of the precise number of instances relies on the provided input. If the input is represented as an “array of strings” (in Terraform terms, a set(string)), the corresponding Terraform configuration appears as follows:

variable "instance_set" {
  type = set(string)
  default = ["Instance A", "Instance B"]
}

resource "aws_instance" "by_set" {
  for_each = var.instance_set
  ami = "ami-0b08bfc6ff7069aff"
  instance_type = "t2.micro"

  tags = {
    Name = each.value
  }
}

In this context, an input variable of type set(string) has been defined. The default value assigned to this variable comprises a pair of strings. With a set length of 2, we anticipate the “aws_instance” resource block to generate two EC2 instances.

The initial attribute in the aws_instance resource block is for_each, which receives the variable value “instance_set” of type set(string). The attributes ami and instance_type remain hardcoded, as they are not directly pertinent to the focal point of this blog post.

Additionally, the Name tag utilizes the each object to reference the value of each string within the instance_set variable.

Upon executing this Terraform configuration, two EC2 instances named “Instance A” and “Instance B” are created. This outcome can be verified through the output of the plan command below.

+ tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

Plan: 2 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

Terraform for_each with a list of strings

In situations where a list(string) is employed instead of a set, Terraform’s built-in function toset() can be employed for the necessary type conversion.

The configuration modifications for utilizing list(string) are straightforward and are illustrated below.

variable "instance_set" {
  type = list(string)
  default = ["Instance A", "Instance B"]
}
resource "aws_instance" "by_set" {
  for_each = toset(var.instance_set)
  ami = "ami-0b08bfc6ff7069aff"
  instance_type = "t2.micro"

  tags = {
    Name = each.value
  }
}

In this article, we’ve delved into diverse methodologies of leveraging for_each meta-arguments to generate multiple instances of resources within a single resource block. By employing input variables such as set(string) and maps, we successfully customized the distinctions among each created resource. Advanced topics, including conditional creation and chaining to reference parent resource attributes established through for_each, in resources dependent on them, were also explored. The implementation of for_each over the count meta-argument enhances readability and renders the Terraform code more maintainable.

Leave a Comment