🐶
Terraform

Terraform: Looping in Resources with for and for_each

By Filip on 11/10/2024

Learn to use powerful `for` and `for_each` constructs in Terraform to efficiently iterate over nested data structures and manage complex resource configurations with ease.

Terraform: Looping in Resources with for and for_each

Table of Contents

Introduction

Terraform provides powerful looping constructs for working with nested data structures. This flexibility allows you to manage complex infrastructure setups efficiently. There are two primary methods for iterating over nested data in Terraform: nested for_each loops and a combination of for and for_each.

Step-by-Step Guide

To iterate over nested data structures in Terraform, you can use nested for_each loops or a combination of for and for_each.

Nested for_each loops:

resource "example_resource" "example" {
  for_each = var.teams
  team_name = each.key

  dynamic "workspace" {
    for_each = each.value.workspaces
    content {
      workspace_name = workspace.value
    }
  }
}

In this example, the outer for_each iterates over the teams map, while the inner for_each iterates over the workspaces list within each team.

Using for within for_each:

resource "example_resource" "example" {
  for_each = var.teams

  dynamic "workspace" {
    for i in range(length(each.value.workspaces)) {
      content {
        workspace_name = each.value.workspaces[i]
      }
    }
  }
}

Here, the for loop iterates over the indices of the workspaces list, and you can access individual elements using the index i.

Accessing nested values:

You can access nested values within the loop using the each object and the appropriate keys. For example, each.value.workspaces[i] accesses the ith workspace name within the current team.

Dynamic blocks:

When working with nested structures, you can use dynamic blocks to create resources based on the nested data. The dynamic block allows you to iterate over a collection and define resources within it.

Important considerations:

  • Ensure that the data structure you're iterating over is in the correct format (list or map).
  • Use meaningful keys for your maps to improve readability.
  • When nesting loops, make sure the inner loop depends on the outer loop's each object.
  • Consider using the flatten function to simplify complex nested structures before iterating.

Code Example

The code provides examples of how to iterate over nested data structures in Terraform using techniques like nested for_each loops, combining for loops within for_each, and flattening nested structures. It showcases creating AWS resources like S3 buckets, security groups, and IAM users with policies based on the nested data. The examples demonstrate how to access specific elements within nested structures and use them to dynamically generate resources and configurations.

1. Nested for_each loops:

This example creates AWS S3 buckets for different teams and their respective workspaces.

variable "teams" {
  type = map(object({
    workspaces = list(string)
  }))
  default = {
    "team_a" = {
      workspaces = ["dev", "staging", "prod"]
    },
    "team_b" = {
      workspaces = ["development", "production"]
    }
  }
}

resource "aws_s3_bucket" "team_buckets" {
  for_each = var.teams

  bucket = "${each.key}-bucket"

  dynamic "versioning" {
    for_each = each.value.workspaces
    content {
      enabled = true
    }
  }

  tags = {
    Team = each.key
  }
}

2. Using for within for_each:

This example creates security groups for different environments and adds ingress rules based on port numbers.

variable "environments" {
  type = map(object({
    ports = list(number)
  }))
  default = {
    "development" = {
      ports = [22, 80, 443]
    },
    "production" = {
      ports = [443, 8443]
    }
  }
}

resource "aws_security_group" "example" {
  for_each = var.environments

  name = "sg-${each.key}"
  description = "Security group for ${each.key} environment"

  dynamic "ingress" {
    for i in range(length(each.value.ports)) {
      content {
        from_port   = each.value.ports[i]
        to_port     = each.value.ports[i]
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
  }
}

3. Accessing nested values and using flatten:

This example demonstrates flattening a nested data structure and creating IAM users with specific policies.

variable "users_data" {
  type = list(object({
    username = string
    teams   = list(string)
    policies = list(string)
  }))
  default = [
    {
      username = "john.doe",
      teams = ["developers", "sre"],
      policies = ["read-only", "admin"]
    },
    {
      username = "jane.doe",
      teams = ["developers"],
      policies = ["read-only"]
    }
  ]
}

locals {
  users = flatten([
    for user in var.users_data : [
      for team in user.teams : {
        username = user.username
        team     = team
        policies = user.policies
      }
    ]
  ])
}

resource "aws_iam_user" "example" {
  for_each = { for user in local.users : "${user.username}-${user.team}" => user }

  name = each.value.username
  tags = {
    Team = each.value.team
  }
}

resource "aws_iam_user_policy_attachment" "example" {
  for_each = { for user in local.users : "${user.username}-${user.team}" => user }

  user = aws_iam_user.example[each.key].name
  policy_arn = "arn:aws:iam::aws:policy/${each.value.policies[0]}"
}

These examples demonstrate different ways to iterate over nested data structures in Terraform. Remember to choose the approach that best suits your specific needs and data structure.

Additional Notes

Choosing the right approach:

  • Nested for_each: Best for situations where you need to iterate over all elements in both the outer and inner collections. Offers clean syntax for direct access to nested values.
  • for within for_each: Useful when you need more control over the iteration process, like skipping elements or accessing indices. Can be less readable than nested for_each for simple cases.
  • flatten function: Consider this when dealing with deeply nested structures or when you need to transform the data into a simpler format before iterating. Can significantly improve readability and maintainability.

Best Practices:

  • Data Validation: Validate the structure and types of your input data to prevent unexpected errors during iteration.
  • Modularity: Break down complex logic into smaller, reusable modules or functions to improve code organization and readability.
  • Comments and Documentation: Clearly document your code, especially when dealing with complex nested structures, to explain the logic and purpose of each loop.
  • Testing: Write comprehensive tests to ensure that your loops are working as expected and handle different data scenarios correctly.

Common Pitfalls:

  • Incorrect data structure: Ensure your data is in the correct format (list or map) before iterating.
  • Key conflicts: When using nested for_each, ensure unique keys to avoid conflicts, especially when creating resources.
  • Scope confusion: Be mindful of the scope of the each object within nested loops. The inner loop's each refers to the current element of the inner collection.
  • Performance: Deeply nested loops or large data sets can impact performance. Consider optimizing your data structures and iteration logic if necessary.

By understanding these concepts and following best practices, you can effectively leverage Terraform's looping constructs to manage complex infrastructure deployments with ease.

Summary

This table summarizes the methods and key points for iterating over nested data structures in Terraform:

Method Description Example Key Points
Nested for_each loops Uses nested for_each blocks to iterate over nested maps and lists. terraform resource "example_resource" "example" { for_each = var.teams team_name = each.key dynamic "workspace" { for_each = each.value.workspaces content { workspace_name = workspace.value } } } - Outer loop iterates over the main data structure.
- Inner loop iterates over nested structures within each element of the outer loop.
for loop within for_each Uses a for loop within a for_each block to iterate over lists within the main data structure. terraform resource "example_resource" "example" { for_each = var.teams dynamic "workspace" { for i in range(length(each.value.workspaces)) { content { workspace_name = each.value.workspaces[i] } } } } - for loop iterates over indices of the nested list.
- Access elements using the index i.
Accessing Nested Values Use the each object and appropriate keys to access nested values within the loop. each.value.workspaces[i] - each refers to the current element of the outer loop.
Dynamic Blocks Use dynamic blocks to create resources based on nested data. terraform dynamic "workspace" { ... } - Allows defining resources iteratively based on the nested data.

General Considerations:

  • Ensure data structure format (list or map) is correct.
  • Use meaningful keys for maps.
  • Inner loop should depend on the outer loop's each object.
  • Consider using the flatten function to simplify complex structures.

Conclusion

Terraform provides powerful looping constructs for working with nested data structures. This flexibility allows you to manage complex infrastructure setups efficiently. There are two primary methods for iterating over nested data in Terraform: nested for_each loops and a combination of for and for_each. Understanding these methods and their respective use cases empowers you to write cleaner, more maintainable Terraform code. By leveraging these looping constructs effectively, you can automate the provisioning of intricate infrastructure with ease and precision. Remember to prioritize code readability and clarity, especially when dealing with nested structures, to ensure maintainability and facilitate collaboration within your team.

References

Were You Able to Follow the Instructions?

😍Love it!
😊Yes
😐Meh-gical
😞No
🤮Clickbait