terraform: create an array of resources using for_each

3 min read

If we need to create multiple resources of the same kind based on a set of objects, we can use the for_each keyword for creating them.

We just need to iterate over a set of objects (if the object it a list we can just convert it using toset()). We will be able to access every object on the list using each. On the following example we are using a list of strings so we can just use each.value getting it's value:

resource "aws_security_group" "demo_multiple_sg" {
  vpc_id = aws_vpc.main.id

  for_each = toset(var.names)

  name = "demo_${each.value}"
}

By setting the variable var.names to the following:

variable "names" {
  type = map(string)
  default = [ "a", "b", "c"]
}

Terraform is going to create the following resources:

$ terraform plan

(...)

Terraform will perform the following actions:

  # aws_security_group.demo_multiple_sg["a"] will be created
  + resource "aws_security_group" "demo_multiple_sg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = "demo_a"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = "vpc-c2aafaab8f13f3b5f"
    }

  # aws_security_group.demo_multiple_sg["b"] will be created
  + resource "aws_security_group" "demo_multiple_sg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = "demo_b"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = "vpc-c2aafaab8f13f3b5f"
    }

  # aws_security_group.demo_multiple_sg["c"] will be created
  + resource "aws_security_group" "demo_multiple_sg" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = "demo_c"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = "vpc-c2aafaab8f13f3b5f"
    }

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

Using one of the latest terraform versions we will also be able to combine it with dynamic blocks since the variable representing the object for each dynamic blocks does not collide with the each variable we are using with for_each:

resource "aws_security_group" "demo_multiple_sg" {
  vpc_id = aws_vpc.main.id

  for_each = toset(var.names)

  name = "demo_${each.value}"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value["from_port"]
      to_port     = ingress.value["to_port"]
      protocol    = ingress.value["protocol"]
      cidr_blocks = ingress.value["cidr_blocks"]
    }
  }

  dynamic "egress" {
    for_each = var.egress_rules
    content {
      from_port   = egress.value["from_port"]
      to_port     = egress.value["to_port"]
      protocol    = egress.value["protocol"]
      cidr_blocks = egress.value["cidr_blocks"]
    }
  }
}

One consideration we need to make is that if we want to provide a list of IDs as output, we will have to iterate over the array of objects to be able to provide the IDs (or any other element). For example:

output "list_sg" {
  value = [
            for instance in aws_security_group.demo_multiple_sg:
                instance.id
  ]
}

Posted on 08/06/2021

Categories