Using Loops in Terraform Code

Author: Vartika Srivastava, Senior Engineer – CloudDevOps


Terraform is an infrastructure as code tool that allows users to define their infrastructure in terms of code.

Loops are an essential feature of Terraform that allows us to automate the repeated tasks in the infrastructure deployment process. Terraform supports the use of loops in various scenarios, including resource creation, configuration, and variable assignments. Some of the primary reasons for using loops in Terraform are:

  • Efficiency: With loops, we can manage multiple resources or environments using a single code block, which is an efficient way of creating and updating infrastructure.
  • Flexibility: We can leverage loops to make the code more flexible and scalable, allowing easy adjustments to the number of infrastructure resources based on requirements.
  • Consistency: Loops provide consistent configuration, especially when dealing with multiple resources, ensuring that all the infrastructure is the same, reducing errors and increasing reliability.
  • Reducing code redundancy: With loops, we eliminate the need for repetitive coding, which can reduce the code base size, making it more manageable and easier to read.

Types of Loops

There are three types of loops in Terraform:

  • count loop
  • for_each loop
  • for loop
count loop

The count loop creates a specified number of identical resources. It iterates over a list of integers with the specified count and creates a resource for each integer in the list.

Example:

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
count = 2
}

In the above example, Terraform creates two EC2 instances.

When count is in use, each instance of a resource or module gets a separate index, representing its place in the order of creation. To get a value from a single resource created in this way, you must refer to it by its index value, e.g. if you wished to see the ID of the 1st instance, you would need to call it as such:

output "1stinstance_id" {
default = aws_instance.example[0].id
}
for_each loop

The for_each loop creates multiple resources with different attributes. It accepts a map or a set of objects as input and creates a separate resource for each map or set element. The resources can access values contained within, via each.key and each.value:

Example using map:

variable "instances" {
type = map(object({
ami = string
instance_type = string
}))
default = {
example-instance-1 = {
ami = "ami-014a674a1c7807f2a"
instance_type = "t2.micro"
}
example-instance-2 = {
ami = "ami-014a674a1c7807f2a"
instance_type = "t2.micro"
}
}
}
resource "aws_instance" "example" {
for_each = var.instances ami = each.value.ami
instance_type = each.value.instance_type
}

In the above example, Terraform creates two EC2 instances with different names and attributes.

Example using set of objects:

variable "users" {
type = set(string)
default = ["alice", "bob", "charlie"]
}
resource "aws_iam_user" "example" {
for_each = var.users
name = each.value
}
for loop

The for loop is designed for selecting elements from complex collections and performing operations on them. Assume you have a list of words (strings), and you want to covert them all into capitals in on go.

Example:

country_list = [
"Amercia",
"jaPan",
"InDia",
"LonDON"
]

To convert them all into capitals you would simply do below steps

[for country in var.country_list : upper(country)]

And it would result in:

["AMERICA", "JAPAN", "INDIA", "LONDON"]

The for expression’s output is dependent on the type of input and the brackets around it. It would have produced a map if you had enclosed it in curly brackets and used a map as the input.

In above example you have seen that we have given input as list and we got the output as list only.

Handling maps using for loop

Let’s see what happens if we give map as input and produce its output as list or give list as input and produce output as map using for loop

Map to List

Example:

locals {
list = {a = 1, b = 2, c = 3}
}
output "result1" {
value = [for k,v in local.list : "${k}-${v}" ]
}
output "result2" {
value = [for k in local.list : k ]
}

Results:

result1 = ["a-1", "b-2", "c-3"]
result2 = [1, 2, 3]

In the above example, k denotes the key and v denotes the value. So, when the input source is a Map, you can access the key and value. We got output as list because of [] square brackets.

List to Map

To return the map type output, the output value should be enclosed in {} curly brackets.

Example:

locals {
list = ["a","b","c"]
}
output "result" {
value = {for i in local.list : i => i }
}

Result:

result = {
"a" = "a"
"b" = "b"
"c" = "c"
}

As you can see, a Map is now the result output type. It’s crucial to keep in mind that when returning a Map, we must include an expression with the =>. You’ll experience the following error if you don’t:

Error: Invalid 'for' expression
  on main.tf line 5, in output "result":
5: value = {for i in local.list : i }

Map to Map

For Map to map result, the input and output both should be enclosed in curly {} brackets.

Example:

locals {
list = {a = 1, b = 2, c = 3}
}
output "result" {
value = {for k,v in local.list : k => v }
}

Result:

result = {
"a" = 1
"b" = 2
"c" = 3
}

So, when a for expression is wrapped around [] square brackets, then the result would be in list, when a for expression is wrapped around curly brackets {}, then the result would be in map and most importantly, when you are returning output as map, the expression elements should be separated by =>.

Handling filters using for loop

One more thing which is also very interesting about for loops is that you can use the for expression to filter the input anyway you like. You can conditionally operate on particular values or not, depending on your stated terms, by including an if clause.

Filter Simple Elements

You want to remove newline character from every element except “London.”

country_list = [
"Amercia\n",
"jaPan\n",
"InDia\n",
"London"
]
[for country in var.country_list : chomp(country) if country != "London"]

Filter Map Elements

In this example, the elements are Maps. We’ll transform the data to only return the values, but only if the b key’s value is greater than 6.

locals {
list = [
{a = 1, b = 5},
{a = 2, b = 6},
{a = 3, b = 7},
{a = 4, b = 8},
]
}
output "list" {
value = [for m in local.list : values(m) if m.b > 6 ]
}

Result:

list = [
[3,7]
[4,8],
]

Filter Inconsistent Map Elements

Let’s say the Map elements are a little bit inconsistent, like some of them lack the b key. We can filter for that.

locals {
list = [
{a = 1, b = 5},
{a = 2},
{a = 3},
{a = 4, b = 8},
]
}
output "list" {
value = [for m in local.list : m if contains(keys(m), "b") ]
}

Result:

list = [
{"a" = 1, "b" = 5},
{"a" = 4, "b" = 8},
]

Loops in Terraform can help in reducing manual work and keep the code DRY (Don’t Repeat Yourself). It allows creating multiple resources based on various inputs and saves time and effort.

Overall, Loops are a useful feature that enhances Terraform’s functionality, making it possible to deploy standardized and scalable infrastructures with minimal effort.

Leave a Reply

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

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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