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 | |
| 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. |
|
| Using count with computed lists | Use count with a list that flattens nested structures. |
|
| Using local values to flatten | Define a flattened list in locals for reuse across resources. |
|
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.