Terraform: Mastering Foreach with List of Maps within a List of Maps

Terraform, a popular Infrastructure as Code (IaC) tool, provides a powerful feature known as foreach to iterate over collections such as lists, sets, and maps. However, when it comes to nested collections, like a list of maps within a list of maps, things can get a bit tricky. This blog post will guide you through the process of using foreach with nested collections in Terraform.

Terraform, a popular Infrastructure as Code (IaC) tool, provides a powerful feature known as foreach to iterate over collections such as lists, sets, and maps. However, when it comes to nested collections, like a list of maps within a list of maps, things can get a bit tricky. This blog post will guide you through the process of using foreach with nested collections in Terraform.

Table of Contents

  1. Understanding Foreach in Terraform
  2. Foreach with List of Maps
  3. Foreach with List of Maps within a List of Maps
  4. Best Practices for Using foreach with List of Maps
  5. Common Errors and Troubleshooting
  6. Conclusion

Understanding Foreach in Terraform

Before diving into nested collections, let’s first understand the foreach construct in Terraform. Foreach is used to create multiple instances of a resource or module. It takes a map or a set of strings, and creates a new instance for each item.

resource "aws_instance" "example" {
  foreach = var.instances

  ami           = "ami-0c94855ba95c574c8"
  instance_type = "t2.micro"

  tags = {
    Name = each.key
  }
}

In this example, var.instances is a map where each key-value pair represents an AWS instance. The each.key and each.value can be used to access the key and value of the current item, respectively.

Foreach with List of Maps

Now, let’s see how we can use foreach with a list of maps. Suppose we have a variable var.subnets that is a list of maps, where each map represents a subnet.

variable "subnets" {
  description = "List of subnets"
  type        = list(map(string))

  default = [
    {
      name = "subnet-1"
      cidr = "10.0.1.0/24"
    },
    {
      name = "subnet-2"
      cidr = "10.0.2.0/24"
    }
  ]
}

We can use foreach to create a subnet for each map in the list.

resource "aws_subnet" "example" {
  foreach = { for s in var.subnets : s.name => s }

  vpc_id     = aws_vpc.example.id
  cidr_block = each.value.cidr

  tags = {
    Name = each.key
  }
}

In this example, we use a for expression to transform the list of maps into a map of maps, which can be used with foreach.

Foreach with List of Maps within a List of Maps

Now, let’s tackle the main topic: using foreach with a list of maps within a list of maps. Suppose we have a variable var.vpcs that is a list of maps, where each map represents a VPC and contains a key subnets that is a list of maps.

variable "vpcs" {
  description = "List of VPCs"
  type        = list(map(any))

  default = [
    {
      name    = "vpc-1"
      cidr    = "10.0.0.0/16"
      subnets = [
        {
          name = "subnet-1"
          cidr = "10.0.1.0/24"
        },
        {
          name = "subnet-2"
          cidr = "10.0.2.0/24"
        }
      ]
    },
    {
      name    = "vpc-2"
      cidr    = "10.0.0.0/16"
      subnets = [
        {
          name = "subnet-3"
          cidr = "10.0.3.0/24"
        },
        {
          name = "subnet-4"
          cidr = "10.0.4.0/24"
        }
      ]
    }
  ]
}

We can use foreach to create a VPC and its subnets for each map in the list.

resource "aws_vpc" "example" {
  foreach = { for v in var.vpcs : v.name => v }

  cidr_block = each.value.cidr

  tags = {
    Name = each.key
  }
}

resource "aws_subnet" "example" {
  for_each = { for v in var.vpcs, s in v.subnets : "${v.name}-${s.name}" => s }

  vpc_id     = aws_vpc.example[split("-", each.key)[0]].id
  cidr_block = each.value.cidr

  tags = {
    Name = each.key
  }
}

In this example, we use a nested for expression to flatten the list of maps within a list of maps into a map of maps. The key of the map is a combination of the VPC name and the subnet name, which ensures uniqueness.

Best Practices for Using foreach with List of Maps

Naming Conventions

Maintain clear and descriptive variable and resource names to enhance code readability. Meaningful names help in understanding the purpose of each element in the data structure.

Variable Validation

Implement robust variable validation to ensure that the input data adheres to expected formats. This reduces the chances of encountering errors during resource provisioning.

Code Organization

Organize your Terraform code in a modular and structured manner. Group related resources and variables, making it easier to manage and maintain your infrastructure codebase.

Common Errors and Troubleshooting

Error: “Cannot iterate over null value”

This error occurs when attempting to iterate over a variable that is null. Ensure that the input data for the variable is properly formatted and contains valid values.

Error: “Index out of range”

When working with indices, it’s crucial to ensure that the index values are within the valid range of the list. This error can be addressed by validating the length of the list before accessing elements.

Error: “Type mismatch”

Ensure that the data types of variables match the expected types. Type mismatches can lead to runtime errors, so validating types is essential.

Conclusion

Terraform’s foreach construct is a powerful tool for managing collections of resources. While it can be a bit tricky to use with nested collections, with a bit of practice and understanding, it becomes a valuable asset in your IaC toolbox. Remember to always ensure uniqueness of keys when using foreach, as Terraform uses these keys to identify individual resources.


About Saturn Cloud

Saturn Cloud is your all-in-one solution for data science & ML development, deployment, and data pipelines in the cloud. Spin up a notebook with 4TB of RAM, add a GPU, connect to a distributed cluster of workers, and more. Request a demo today to learn more.