In this article, we will look at how we can handle a ‘for loop’ in Terraform. Meta-arguments such as count
and for_each
can be used for different use cases when you want to repeat a set of actions multiple times.
We will look at each one in turn with examples, before looking at how to use the Terraform for
expression with an if
clause. Let’s dive in!
You will learn the following:
By default, a resource block configures one object. Using a loop, you can manage several similar objects without writing a separate block for each one. This reduces the amount of code you need to write and makes your scripts cleaner.
Two meta-arguments can be used to do this in Terraform:
count
– This looping construct creates a fixed number of resources based on a count value.for_each
– This looping construct allows you to create multiple instances of a resource based on a set of input values, such as a list or map.
If the resources you are provisioning are identical or nearly identical, then count
is a safe bet. However, if elements of the resources change between the different instances, then for_each
is the way to go.
You can also use the for
expression to transform a value on each item in a list or map. Use for
when you need to iterate over a set of input values and perform some action on each value.
Below you can find the table comparison of Terraform count
, for_each
and for
.
Construct | Type | Description | Use Case |
Count | Meta-Argument | Based on a count value | Resources you are provisioning are identical |
For_each | Meta-Argument | Based on a set of input values | Resources change between the different instances |
For | Expression | Based on a set of input values | Transform a value |
The example below uses the count
meta-argument to loop through a list of storage account names and create a storage account with the name specified for each.
The name argument uses the count.index
expression to access the current index of the loop (starting from 0) and select the storage account name from the storage_account_names
list using the index. The rest of the arguments are the same for each storage account.
variable "storage_account_names" {
type = list(string)
default = ["jackuksstr001", "jackuksstr002", "jackuksstr003"]
}
resource "azurerm_resource_group" "example" {
name = "storage-rg"
location = "UK South"
}
resource "azurerm_storage_account" "my_storage" {
count = length(var.storage_account_names)
name = var.storage_account_names[count.index]
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
}
The example below uses a for_each
loop to iterate through a list of the same storage account names and create a storage account with the name specified for each. The rest of the arguments are the same for each storage account.
The result will be the same as the example using count
above.
variable "storage_account_names" {
type = list(string)
default = ["jackuksstr001", "jackuksstr002", "jackuksstr003"]
}
resource "azurerm_resource_group" "example" {
name = "storage-rg"
location = "UK South"
}
resource "azurerm_storage_account" "my_storage" {
for_each = toset(var.storage_account_names)
name = each.value
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
}
The example below builds on the previous one and shows how to output a list of storage account IDs from the given list. The for
expression is used to iterate over the storage_account_names
list and retrieve the ID for each storage account instance with the corresponding name.
variable "storage_account_names" {
type = list(string)
default = ["jackuksstr001", "jackuksstr002", "jackuksstr003"]
}
resource "azurerm_resource_group" "example" {
name = "storage-rg"
location = "UK South"
}
resource "azurerm_storage_account" "my_storage" {
for_each = toset(var.storage_account_names)
name = each.value
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
}
output "storage_account_names" {
value = [
for storage in var.storage_account_names:
azurerm_storage_account.my_storage.example[storage].id
]
}
A for
expression can also include an if
clause to filter elements from the source variable, producing a value with fewer elements than the source value, and is commonly used to split lists based on a condition.
The syntax looks like the below:
[for VAR in COLLECTION: IF CONDITION_EXPRESSION: VAR]
VAR
is the name of the variable that represents each item in the collection, COLLECTION
is the collection to be filtered, and CONDITION_EXPRESSION
is the boolean expression that determines whether each item should be included in the filtered collection.
In the example below, we use the for
expression with the if
condition to output a list of storage account names that have the account_replication_type
set to GRS
. This example will output the three storage account names provided in the storage_account_names
variable, as they will all have their account_replication_type
set to GRS
.
variable "storage_account_names" {
type = list(string)
default = ["jackuksstr001", "jackuksstr002", "jackuksstr003"]
}
resource "azurerm_resource_group" "example" {
name = "storage-rg"
location = "UK South"
}
resource "azurerm_storage_account" "my_storage" {
count = length(var.storage_account_names)
name = var.storage_account_names[count.index]
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
}
locals {
grs_storage_accounts = [for sa in azurerm_storage_account.my_storage: sa if sa.account_replication_type == "GRS"]
}
output "grs_storage_account_names" {
value = [for sa in local.grs_storage_accounts: sa.name]
}
The if
clause can be used to conditionally include or exclude certain expressions based on a boolean condition.
The syntax for using the if
clause in an expression is as follows:
${condition ? true_value : false_value}
In the example below, we use the if
condition to set the account_replication_type
to GRS
if the environment
variable is set to prod
, if it is not, then the account_replication_type
will be set to LRS
.
Because the default value for the environment
variable is set to prod
in the below example, the three storage accounts created using the for_each
loop will all have their account_replication_type
set to GRS
.
variable "storage_account_names" {
type = list(string)
default = ["jackuksstr001", "jackuksstr002", "jackuksstr003"]
}
variable "environment" {
default = "prod"
}
resource "azurerm_resource_group" "example" {
name = "storage-rg"
location = "UK South"
}
resource "azurerm_storage_account" "my_storage" {
for_each = toset(var.storage_account_names)
name = each.value
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "${var.environment == "prod" ? "GRS" : "LRS"}"
}
Terraform has two meta-arguments for performing for loops, count
and for_each
. count
should be used when you are provisioning multiple resources that are identical or near identical, and for_each
should be used when resources change between different instances.
The for
expression can be used to transform values and can be used with the if
clause to include or exclude expressions based on a boolean condition.
We encourage you also to explore how Spacelift makes it easy to work with Terraform. If you need any help managing your Terraform infrastructure, building more complex workflows based on Terraform, and managing AWS credentials per run, instead of using a static pair on your local machine, Spacelift is a fantastic tool for this. It supports Git workflows, policy as code, programmatic configuration, context sharing, drift detection, and many more great features right out of the box. You can check it for free, by creating a trial account.
Manage Terraform Better with Spacelift
Build more complex workflows based on Terraform using policy as code, programmatic configuration, context sharing, drift detection, resource visualization and many more.
Terraform Loops and Conditionals Cheatsheet
Grab our ultimate cheat sheet PDF to keep your workflows DRY.