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.
Terraform doesn’t have traditional “for” or “while” loops like other programming languages. Instead, it has a unique approach to loops primarily based on:
count
: Simple looping for creating multiple instances of a resource.for_each
: A more advanced approach for looping over maps or sets, allowing for flexible resource configurations.for
expressions: Typically used within variables or resource attributes to generate lists or maps.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.
resource "azurerm_resource_group" "countExample" {
count = 3
name = "resourceGroup-${count.index}"
location = "East US"
}
Bits and That Technology BlogIn this example:
count
parameter creates three resource groupscount.index
, which represents the zero-based index of each instance (i.e 0,1,2).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.
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.
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 BlogIn this example:
random_string
resource: Generates an 8-character string, lowercase, with no special characters, making it compliant with Azure storage account naming requirements.name
property in azurerm_storage_account
: Uses "mystorage${random_string.storage_account_suffix.result}"
to append the random string to "mystorage"
, ensuring uniqueness.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.
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.
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:
for
expression iterates over each item in the regions
list.-a
to form a new list of availability zones, such as ["us-west-1-a", "us-west-2-a", "us-east-1-a"]
.TERRAFORM PLAN Output
Changes to Outputs:
+ region_availability_zones = [
+ "us-west-1-a",
+ "us-west-2-a",
+ "us-east-1-a",
]
for
Expressionvariable "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 BlogIn this example:
if
statement filters the list, including only names that start with “web.”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
count
for Simple Replication: When you need a straightforward duplication of resources without customization, count
is your best option.for_each
for Custom Configurations: For resources that require different configurations, go with for_each
and use maps or sets for flexible definitions.for
Expressions Thoughtfully: for
expressions offer powerful list and map generation but can become complex. Keep them readable and concise for maintainability.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