Skip to main content

Using Looping Techniques

1. Using Nested For Expressions

Expression Description Example
for Iterates over a list or map for item in list
for with index Iterates over a list with index for i, item in list
for with key and value Iterates over a map with key and value for key, value in map

Example: Nested For Loop with Lists

resource "aws_instance" "web" {
  count = length(var.instances)
  ami           = var.ami_id
  instance_type = var.instance_types[count.index]
}

Example: Nested For Loop with Maps

resource "aws_security_group" "sg" {
  for_each = var.security_groups
  name        = each.key
  description = each.value.description
}

Example: Nested For Loop with Index

resource "aws_subnet" "subnet" {
  count = length(var.subnet_cidr_blocks)
  cidr_block = var.subnet_cidr_blocks[count.index]
  availability_zone = "us-east-1${count.index + 1}"
}

2. Using For-Each with Dynamic Blocks

Property Description Example
for_each Iterates over a map or set to create multiple resources or blocks for_each = var.instances
each.key Accesses the key of the current iteration name = each.key
each.value Accesses the value of the current iteration ami = each.value.ami_id

Example: Creating Multiple S3 Buckets

resource "s3_bucket" "bucket" {
  for_each = var.bucket_names

  bucket = each.key
  acl    = "private"

  tags = {
    Name = each.key
  }
}

Example: Using for_each with Dynamic Blocks

resource "aws_s3_bucket" "bucket" {
  for_each = var.bucket_configs

  bucket = each.value.name
  acl    = each.value.acl

  dynamic "website" {
    for_each = each.value.website_config != null ? [each.value.website_config] : []
    content {
      index_document = website.value.index_document
      error_document = website.value.error_document
    }
  }
}
Note: The dynamic block allows you to conditionally create nested blocks inside a resource or module. It's particularly useful when the structure of nested blocks varies based on input.

3. Filtering with For Expressions

Expression Description Example
for ... if Filter elements in a for expression based on a condition filter([1, 2, 3, 4], x -> x > 2</code>)
for ... if with index Filter elements using the index and a condition filter(["a", "b", "c"], x, i -> i < 2</code>)

Example: Filtering Lists

var numbers = [1, 2, 3, 4, 5];
var filtered = numbers.filter(x => x > 3);
console.log(filtered); // [4, 5]

Example: Filtering with Index

var items = ["apple", "banana", "cherry"];
var filtered = items.filter(item, index => index < 2);
console.log(filtered); // ["apple", "banana"]

4. Transforming Data with For

Function Description Example
for Transforms a list into another list by applying a function to each element for (item in list) <transform>
for_each Iterates over a map or set to create multiple resources or outputs for_each = { key1 = value1, key2 = value2 }

Example: Using for_each to create multiple S3 buckets

resource "aws_s3_bucket" "bucket" {
  for_each = {
    bucket1 = "us-west-1"
    bucket2 = "us-east-1"
  }
  bucket = each.key
  region = each.value
}

Example: Using for to transform a list of strings

locals {
  uppercase_names = [
    for name in var.names : upper(name)
  ]
}
Note: The for_each loop is particularly useful for creating multiple resources with different configurations based on a map or set of keys.

5. Using Splat with For

Property Description Example
count Number of instances to create count = 3
for_each Iterates over a map or set of strings for_each = { a = "1", b = "2" }
splat Accesses attributes from a list or map of objects aws_instance.example[*].id

Example: Using Splat with For

resource "aws_instance" "web" {
  count = 3
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

output "instance_ids" {
  value = aws_instance.web[*].id
}
Note: The [*] operator is used to access all elements in a list or map.

6. Combining Count and For-Each

When using count and for_each together, Terraform applies for_each to each instance created by count. This allows for more complex looping patterns, such as creating multiple resources with different configurations based on a list of values.

Attribute Description Example
count Number of instances to create count = 3
for_each Map or set of strings to iterate over for_each = var.instances
each.key Key of the current iteration in for_each each.key
each.value Value of the current iteration in for_each each.value

Example: Combining Count and For-Each

resource "aws_instance" "web" {
  count = 2
  for_each = toset(["us-east-1", "us-west-2"])

  ami           = "ami-12345678"
  instance_type = "t2.micro"
  availability_zone = each.value
}
Note: When combining count and for_each, the for_each loop is applied to each instance created by count. The resulting resource will have count * length(for_each) instances.
Warning: Be cautious when combining count and for_each, as it can lead to a large number of resources being created. Ensure that the combination aligns with your desired infrastructure configuration.

7. Using For with Maps and Objects

Property Description Example
for_each Iterates over a map or set of strings to create multiple resources or blocks
resource "aws_instance" "web" {
  for_each = var.instances
  ami      = each.value.ami
  instance_type = each.value.type
}
each.key Accesses the key of the current iteration in a for_each loop each.key
each.value Accesses the value of the current iteration in a for_each loop each.value

Example: Creating Multiple S3 Buckets

variable "buckets" {
  type = map(object({
    region = string
    versioning = bool
  }))
  default = {
    "bucket1" = {
      region = "us-west-1"
      versioning = true
    }
    "bucket2" = {
      region = "us-east-1"
      versioning = false
    }
  }
}

resource "aws_s3_bucket" "bucket" {
  for_each = var.buckets
  bucket = each.key
  region = each.value.region

  versioning {
    enabled = each.value.versioning
  }
}
Note: The for_each loop creates a resource for each key-value pair in the map.
Warning: Changing keys in a for_each map will cause resources to be destroyed and recreated.

8. Using For with Conditional Logic

Feature Description Usage
for_each Iterates over a map or set to create multiple resources or blocks for_each = var.instances
count Creates a specified number of resources or blocks count = 3
condition Conditionally evaluates whether to create a resource or block count = var.enable_resource ? 1 : 0

Example: Using for_each with conditional logic

resource "aws_instance" "web" {
  for_each = var.enable_web ? var.web_instances : {}
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  tags = {
    Name = "web-${each.key}"
  }
}

Example: Using count with conditional logic

resource "aws_security_group" "allow_ssh" {
  count = var.enable_ssh ? 1 : 0
  
  name        = "allow-ssh"
  description = "Allow SSH access"
  
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
Note: for_each is preferred over count when iterating over a map or set, as it provides better diffing and tracking of individual items.

9. Flattening Nested Loops

Flattening nested loops in Terraform involves restructuring loop logic to reduce nesting levels and improve readability. This is particularly useful when working with complex data structures or when trying to simplify conditional logic.

Approach Description Example
Using for_each with flattened lists Flatten nested loops into a single for_each by combining keys or indices.
data "aws_instances" "flattened" {
  for_each = { for instance in var.instances : instance.name => instance }
}
Using count with computed lists Use count with a list that flattens nested structures.
resource "aws_instance" "flattened" {
  count = length(flatten(var.nested_list))
  ami   = var.ami_id
}
Using local values to flatten Define a flattened list in locals for reuse across resources.
locals {
  flattened_list = flatten([
    for item in var.nested_data : [
      for sub_item in item.sub_items : {
        name = "${item.name}-${sub_item.id}"
        value = sub_item.value
      }
    ]
  ])
}

Example: Flattening nested loops with local values

locals {
  nested_data = [
    {
      name = "group1"
      sub_items = [
        { id = "0", value = "val1" },
        { id = "1", value = "val2" }
      ]
    },
    {
      name = "group2"
      sub_items = [
        { id = "0", value = "val3" },
        { id = "1", value = "val4" }
      ]
    }
  ]

  flattened_list = flatten([
    for item in local.nested_data : [
      for sub_item in item.sub_items : {
        name = "${item.name}-${sub_item.id}"
        value = sub_item.value
      }
    ]
  ])
}

resource "aws_instance" "example" {
  count = length(local.flattened_list)
  ami   = var.ami_id
  tags = {
    name = local.flattened_list[count.index].name
  }
}
Note: Flattening nested loops can improve readability but may reduce performance in some cases due to increased computation during plan/apply phases.

10. Using For with Zip and Range

Function Description Example
zipmap Combines two lists into a map where the first list is keys and the second is values zipmap(keys, values)
range Generates a list of numbers from 0 to n-1 range(3)

Example: Using zipmap with range

resource "aws_instance" "web" {
  count = length(var.instance_names)

  ami           = var.ami_id
  instance_type = var.instance_types[count.index]

  tags = zipmap(
    var.instance_names,
    range(length(var.instance_names))
  )
}

Example: Using range with for_each

resource "aws_route" "main" {
  for_each = zipmap(
    var.route_names,
    range(length(var.route_names))
  )

  route_table_id = aws_route_table.main.id
  destination_cidr_block = var.cidr_blocks[each.value]
  gateway_id = aws_internet_gateway.main.id
}
Note: The zipmap function is useful for creating maps from lists, especially when you want to associate names with indices or other data. The range function is helpful for generating sequential numbers, which can be used as indices or counters in loops.

Was this article helpful?