Posted: Wednesday, November 6, 2024

Word Count: 2336

Reading Time: 11 minutes


Managing and scaling configurations in a repetitive and automated way becomes essential. Terraform offers powerful looping constructs that let you handle multiple resources and configurations more efficiently. Lets dive into the types of loop statements in Terraform, including for_each and for expressions, with practical examples to show how you can leverage them in your IaC (Infrastructure as Code) projects.

Types of Loops in Terraform

Terraform doesn’t have traditional “for” or “while” loops like other programming languages. Instead, it has a unique approach to loops primarily based on:

1. Using count for Simple Loops

The count parameter is one of the most basic ways to repeat resources in Terraform. It allows you to specify the number of instances to create, making it ideal for simple replication scenarios.

Terraform – Simple Loop
resource "azurerm_resource_group" "countExample" {
    count = 3
    name     = "resourceGroup-${count.index}"
    location = "East US"
}
Bits and That Technology Blog

In this example:

Note: count works best when you don’t need individual configurations for each resource.

TERRAFORM PLAN OUTPUT

Terraform will perform the following actions:

# azurerm_resource_group.example[0] will be created
+ resource "azurerm_resource_group" "countExample" {
+ id = (known after apply)
+ location = "eastus"
+ name = "resourceGroup-0"
}

# azurerm_resource_group.example[1] will be created
+ resource "azurerm_resource_group" "countExample" {
+ id = (known after apply)
+ location = "eastus"
+ name = "resourceGroup-1"
}

# azurerm_resource_group.example[2] will be created
+ resource "azurerm_resource_group" "countExample" {
+ id = (known after apply)
+ location = "eastus"
+ name = "resourceGroup-2"
}

Plan: 3 to add, 0 to change, 0 to destroy.

2. for_each for Flexible and Dynamic Loops

Unlike count, for_each is designed for cases where you need more control over each instance in a resource set. It’s particularly useful when looping over maps or sets, allowing you to define custom attributes for each instance.

Terraform – Flexible Loop
provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
} 

variable "resource_group_names" {
  type    = list(string)
  default = ["RG-Dev", "RG-Test", "RG-Prod"]
}

resource "azurerm_resource_group" "countExample" {
    count = 3
    name     = "resourceGroup-${count.index}"
    location = "East US"
}

resource "random_string" "storage_account_suffix" {
  length  = 8
  upper   = false
  special = false
}

variable "storage_accounts" {
  type = map(object({
    sku       = string
    access_tier = string
  }))
  default = {
    "prodstorage" = { sku = "LRS", access_tier = "Hot" }
    "devstorage"  = { sku = "GRS", access_tier = "Cool" }
  }
}

resource "azurerm_storage_account" "storage" {
  for_each            = var.storage_accounts
  name                = "${each.key}-${random_string.storage_account_suffix.result}"
  resource_group_name = azurerm_resource_group.countExample[0].name
  location            = azurerm_resource_group.countExample[0].location
  account_tier        = "Standard"
  account_replication_type = each.value.sku

  tags = {
    environment = each.key
    access_tier = each.value.access_tier
  }
}
Bits and That Technology Blog

In this example:

Key Benefit: for_each is excellent for resource configurations that need individual customization.

TERRAFORM PLAN OUTPUT

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create

Terraform will perform the following actions:

# azurerm_storage_account.storage["devstorage"] will be created
+ resource "azurerm_storage_account" "storage" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "GRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ cross_tenant_replication_enabled = false
+ default_to_oauth_authentication = false
+ dns_endpoint_type = "Standard"
+ https_traffic_only_enabled = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ local_user_enabled = true
+ location = "eastus"
+ min_tls_version = "TLS1_2"
+ name = (known after apply)
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_blob_internet_endpoint = (known after apply)
+ primary_blob_internet_host = (known after apply)
+ primary_blob_microsoft_endpoint = (known after apply)
+ primary_blob_microsoft_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_dfs_internet_endpoint = (known after apply)
+ primary_dfs_internet_host = (known after apply)
+ primary_dfs_microsoft_endpoint = (known after apply)
+ primary_dfs_microsoft_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_file_internet_endpoint = (known after apply)
+ primary_file_internet_host = (known after apply)
+ primary_file_microsoft_endpoint = (known after apply)
+ primary_file_microsoft_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_queue_microsoft_endpoint = (known after apply)
+ primary_queue_microsoft_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_table_microsoft_endpoint = (known after apply)
+ primary_table_microsoft_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ primary_web_internet_endpoint = (known after apply)
+ primary_web_internet_host = (known after apply)
+ primary_web_microsoft_endpoint = (known after apply)
+ primary_web_microsoft_host = (known after apply)
+ public_network_access_enabled = true
+ queue_encryption_key_type = "Service"
+ resource_group_name = "resourceGroup-0"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_blob_internet_endpoint = (known after apply)
+ secondary_blob_internet_host = (known after apply)
+ secondary_blob_microsoft_endpoint = (known after apply)
+ secondary_blob_microsoft_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_dfs_internet_endpoint = (known after apply)
+ secondary_dfs_internet_host = (known after apply)
+ secondary_dfs_microsoft_endpoint = (known after apply)
+ secondary_dfs_microsoft_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_file_internet_endpoint = (known after apply)
+ secondary_file_internet_host = (known after apply)
+ secondary_file_microsoft_endpoint = (known after apply)
+ secondary_file_microsoft_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_queue_microsoft_endpoint = (known after apply)
+ secondary_queue_microsoft_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_table_microsoft_endpoint = (known after apply)
+ secondary_table_microsoft_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ secondary_web_internet_endpoint = (known after apply)
+ secondary_web_internet_host = (known after apply)
+ secondary_web_microsoft_endpoint = (known after apply)
+ secondary_web_microsoft_host = (known after apply)
+ sftp_enabled = false
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
+ tags = {
+ "access_tier" = "Cool"
+ "environment" = "devstorage"
}

+ blob_properties (known after apply)

+ network_rules (known after apply)

+ queue_properties (known after apply)

+ routing (known after apply)

+ share_properties (known after apply)
}

# azurerm_storage_account.storage["prodstorage"] will be created
+ resource "azurerm_storage_account" "storage" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ cross_tenant_replication_enabled = false
+ default_to_oauth_authentication = false
+ dns_endpoint_type = "Standard"
+ https_traffic_only_enabled = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ local_user_enabled = true
+ location = "eastus"
+ min_tls_version = "TLS1_2"
+ name = (known after apply)
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_blob_internet_endpoint = (known after apply)
+ primary_blob_internet_host = (known after apply)
+ primary_blob_microsoft_endpoint = (known after apply)
+ primary_blob_microsoft_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_dfs_internet_endpoint = (known after apply)
+ primary_dfs_internet_host = (known after apply)
+ primary_dfs_microsoft_endpoint = (known after apply)
+ primary_dfs_microsoft_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_file_internet_endpoint = (known after apply)
+ primary_file_internet_host = (known after apply)
+ primary_file_microsoft_endpoint = (known after apply)
+ primary_file_microsoft_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_queue_microsoft_endpoint = (known after apply)
+ primary_queue_microsoft_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_table_microsoft_endpoint = (known after apply)
+ primary_table_microsoft_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ primary_web_internet_endpoint = (known after apply)
+ primary_web_internet_host = (known after apply)
+ primary_web_microsoft_endpoint = (known after apply)
+ primary_web_microsoft_host = (known after apply)
+ public_network_access_enabled = true
+ queue_encryption_key_type = "Service"
+ resource_group_name = "resourceGroup-0"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_blob_internet_endpoint = (known after apply)
+ secondary_blob_internet_host = (known after apply)
+ secondary_blob_microsoft_endpoint = (known after apply)
+ secondary_blob_microsoft_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_dfs_internet_endpoint = (known after apply)
+ secondary_dfs_internet_host = (known after apply)
+ secondary_dfs_microsoft_endpoint = (known after apply)
+ secondary_dfs_microsoft_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_file_internet_endpoint = (known after apply)
+ secondary_file_internet_host = (known after apply)
+ secondary_file_microsoft_endpoint = (known after apply)
+ secondary_file_microsoft_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_queue_microsoft_endpoint = (known after apply)
+ secondary_queue_microsoft_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_table_microsoft_endpoint = (known after apply)
+ secondary_table_microsoft_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ secondary_web_internet_endpoint = (known after apply)
+ secondary_web_internet_host = (known after apply)
+ secondary_web_microsoft_endpoint = (known after apply)
+ secondary_web_microsoft_host = (known after apply)
+ sftp_enabled = false
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
+ tags = {
+ "access_tier" = "Hot"
+ "environment" = "prodstorage"
}

+ blob_properties (known after apply)

+ network_rules (known after apply)

+ queue_properties (known after apply)

+ routing (known after apply)

+ share_properties (known after apply)
}

# random_string.storage_account_suffix will be created
+ resource "random_string" "storage_account_suffix" {
+ id = (known after apply)
+ length = 8
+ lower = true
+ min_lower = 0
+ min_numeric = 0
+ min_special = 0
+ min_upper = 0
+ number = true
+ numeric = true
+ result = (known after apply)
+ special = false
+ upper = false
}

Plan: 6 to add, 0 to change, 0 to destroy.

3. for Expressions for Variables and Resource Attributes

The for expression in Terraform allows you to generate lists and maps within variables or resource attributes. This loop construct is highly flexible and can be used in many creative ways.

Example: Generating a List with a for Expression

Terraform – For Expression
variable "regions" {
  type    = list(string)
  default = ["us-west-1", "us-west-2", "us-east-1"]
}

output "region_availability_zones" {
  value = [for region in var.regions : "${region}-a"]
}
Bits and That Technology Blog


In this example:

TERRAFORM PLAN Output

Changes to Outputs:
+ region_availability_zones = [
+ "us-west-1-a",
+ "us-west-2-a",
+ "us-east-1-a",
]

Example: Creating a Map with a Conditional for Expression

Terraform – Map with Conditional Statement
variable "instance_names" {
  type    = list(string)
  default = ["web1", "web2", "db1", "db2"]
}

output "web_server_map" {
  value = { for name in var.instance_names : name => upper(name) if startswith(name, "web") }
}
Bits and That Technology Blog

In this example:

Tip: Use for expressions to streamline complex variable definitions, generate dynamic lists, and build customized maps.

TERRAFORM PLAN Output

Changes to Outputs:
+ web_server_map = {
+ web1 = "WEB1"
+ web2 = "WEB2"
}

Best Practices for Loops in Terraform

  1. Use count for Simple Replication: When you need a straightforward duplication of resources without customization, count is your best option.
  2. Leverage for_each for Custom Configurations: For resources that require different configurations, go with for_each and use maps or sets for flexible definitions.
  3. Apply for Expressions Thoughtfully: for expressions offer powerful list and map generation but can become complex. Keep them readable and concise for maintainability.

Conclusion

Looping in Terraform enables more efficient resource management and helps you avoid code duplication. With count, for_each, and for expressions, you can scale resources dynamically while maintaining flexibility in configuration. By understanding how and when to use each loop construct, you’ll be able to simplify your code, make it more manageable, and adapt it to evolving infrastructure requirements.

Terraform’s loop constructs might take some practice to master, however leveraging these tools help build dynamic and reusable IaC solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *