🐶
Terraform

Terraform 0.12: Looping Through Lists of Objects

By Filip on 10/05/2024

Learn how to iterate over a list of objects in Terraform 0.12 using the powerful for_each loop for efficient and dynamic infrastructure management.

Terraform 0.12: Looping Through Lists of Objects

Table of Contents

Introduction

In Terraform, using the for_each construct with a list of objects requires a map input where keys uniquely identify each object. This ensures proper resource creation during iteration. Let's break down how to achieve this: First, define your list of objects with their respective attributes. Next, transform this list into a map, leveraging a unique attribute from your objects as the key. This step is crucial for for_each to function correctly. Finally, within your resource block, access the attributes of each object using the each.value syntax. Keep in mind some important considerations: always ensure your map keys are unique to prevent errors. If your list lacks inherently unique attributes, consider using toset() for conversion, although this will disregard the original order. Lastly, depending on your scenario, using count with element() might be a more suitable alternative to for_each, particularly when handling lists of simple values. Remember to tailor this approach to your specific use case and resource types.

Step-by-Step Guide

To use for_each with a list of objects in Terraform, you need to provide a map where the keys are unique strings. These keys will be used to identify each instance of the resource created by the for_each loop. Here's how:

  1. Define your list of objects:

    variable "my_objects" {
      default = [
        {
          name = "object1"
          value = "value1"
        },
        {
          name = "object2"
          value = "value2"
        }
      ]
    }
  2. Convert the list to a map using a suitable key: You can use the name attribute from your objects as the key:

    resource "example_resource" "example" {
      for_each = { for obj in var.my_objects : obj.name => obj }
    
      # Access object attributes within the resource block
      name  = each.value.name
      value = each.value.value
    }

    In this example:

    • for_each iterates over var.my_objects.
    • obj.name => obj creates a map entry for each object, using its name as the key and the entire object as the value.
    • Inside the resource block, each.value refers to the current object from the map.
  3. Access object attributes within the resource: Use each.value followed by the attribute name to access individual attributes of each object within the resource block.

Important Considerations:

  • Unique Keys: Ensure the keys used in your map are unique to avoid errors.
  • Set Conversion: If your list doesn't have inherently unique attributes, you can use toset() to convert it into a set of strings, but this will discard the original order of the list.
  • Alternative to for_each: In some cases, using count with element() might be more suitable than for_each, especially when dealing with lists of simple values.

Remember to adapt this approach to your specific use case and resource types.

Code Example

This Terraform code defines a variable "servers" containing a list of server objects, each with hostname, IP address, and disk size. It then uses a for_each loop to create a Google Compute Instance resource for each server in the list, setting the instance name, disk size, and IP address based on the values from the corresponding server object.

# Define a variable with a list of objects
variable "servers" {
  default = [
    {
      hostname = "server-01"
      ip_address = "192.168.1.10"
      disk_size = 50
    },
    {
      hostname = "server-02"
      ip_address = "192.168.1.11"
      disk_size = 100
    }
  ]
}

# Create a cloud resource for each server object
resource "google_compute_instance" "default" {
  for_each = { for server in var.servers : server.hostname => server }

  name         = each.value.hostname
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
      size  = each.value.disk_size # Accessing object attribute
    }
  }

  network_interface {
    network = "default"
    access_ip = each.value.ip_address # Accessing object attribute
  }
}

Explanation:

  1. Variable servers: This variable defines a list of objects, each representing a server with attributes like hostname, ip_address, and disk_size.

  2. for_each in google_compute_instance:

    • We use for_each to create one google_compute_instance resource for each object in the var.servers list.
    • server.hostname => server creates a map where the key is the hostname of each server, and the value is the entire server object.
  3. Accessing Attributes:

    • Inside the resource block, each.value refers to the current server object being processed.
    • We use each.value.disk_size and each.value.ip_address to access the respective attributes of each server object and use them to configure the google_compute_instance resource.

This example demonstrates how to use for_each with a list of objects to create multiple instances of a resource, each configured with values from the corresponding object. Remember to choose a unique key from your objects for the map to avoid errors.

Additional Notes

Choosing the Right Key:

  • Meaningful Keys: While any unique attribute can be a key, choosing one that reflects the object's purpose makes your code more readable. For example, using server.hostname as the key clearly indicates that each resource represents a specific server.
  • Composite Keys: If no single attribute guarantees uniqueness, you can combine multiple attributes using string interpolation:
    for_each = { for obj in var.my_objects : "${obj.attr1}-${obj.attr2}" => obj }

Handling Changes:

  • Key Changes: Be cautious when changing the key attribute of objects in your list. Terraform might interpret this as deleting and recreating resources, potentially leading to data loss.
  • State Management: Terraform's state file tracks resources created with for_each. Inspecting the state file can be helpful for understanding how resources map to your objects.

Best Practices:

  • Modularity: For complex scenarios, consider defining a separate module for the resources created within the for_each loop. This improves code organization and reusability.
  • Comments: Clearly document the purpose of your for_each loop and the logic behind your key selection to enhance code maintainability.

Beyond the Basics:

  • Nested for_each: You can nest for_each loops to iterate over complex data structures, but ensure your keys remain unique across all levels.
  • Conditional Creation: Use for expressions with if clauses within your for_each loop to conditionally create resources based on object attributes.

By understanding these nuances and best practices, you can leverage the power of for_each effectively to manage collections of objects and create dynamic infrastructure with Terraform.

Summary

Feature Description
Defining Objects Define a list of objects, each containing the necessary attributes.
Creating a Map Convert the list into a map using a unique attribute from each object as the key.
Iterating with for_each Use the for_each meta-argument with the created map to iterate over the objects.
Accessing Attributes Access individual object attributes within the resource block using each.value.[attribute_name].
Unique Keys Ensure the keys used in the map are unique to avoid errors.
Alternatives Consider using toset() for lists without unique attributes or count with element() for simple lists.

Conclusion

Mastering the use of for_each with lists of objects is crucial for writing dynamic and efficient Terraform code. By understanding how to define objects, convert lists to maps with unique keys, and access object attributes within resource blocks, you can leverage this powerful construct to manage collections of resources effectively. Remember to consider the important factors of unique keys, alternative iteration methods, and best practices for code clarity and maintainability. As you become more comfortable with for_each, explore advanced techniques like nested loops and conditional resource creation to unlock the full potential of Terraform for your infrastructure automation needs.

References

Were You Able to Follow the Instructions?

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