How to use Terraform functions like a pro

In Terraform, functions serve as building blocks for manipulating and transforming data within your configuration files. They add a programmability element to an otherwise declarative language.

When I first started with Terraform to write configuration to deploy infrastructure on AWS and GCP, I did not know how powerful Terraform is.

I wrote configuration code like an armature — repeated code blocks, hardcoded values, and bloated and inflexible infrastructure configuration code.

It took me a while to start using Terraform’s built-in functions, and everything changed. I learned to leverage Terraform functions in my configuration code. My terraform code got 10x more flexible, manageable, readable, scalable, and DRY.

Complete guide to using Terraform functions in your infrastructure code

The Terraform language includes several built-in functions you could call from within expressions to transform and combine values.

Note: The Terraform language does not support user-defined functions, and so only the functions built into the language are available for use.

The most commonly used Terraform functions

Terraform list functions

  • formatlist(): formatlist produces a list of strings by formatting several other values according to a specification string.

It’s particularly useful when you want to create a formatted string by combining elements from a list, and you want to apply a consistent formatting pattern to those.

elements.locals {
fruits = ["apple", "banana", "orange"]
formatted_list = formatlist("I like %s.", local.fruits...)
}
output "formatted_list_output" {
value = local.formatted_list
}
-> formatted_list_output = [ "I like apple.", "I like banana.", "I like orange.",]

  • flatten(): flatten function transforms a nested list or nested tuple into a flat list.

This can be useful when you have a list of lists or a list of tuples, and you want to combine all the elements into a single flat list.

locals {
nested_list = [
["item1", "item2"],
["item3", "item4", "item5"],
["item6"],
]
flat_list = flatten(local.nested_list)
}
output "flat_list_output" {
value = local.flat_list
}

  • concat(): The concat function concatenates two or more lists or strings together.
It’s commonly used when you want to combine multiple lists or strings into a single list or string.variable "list1" {
default = ["a", "b", "c"]
}
variable "list2" {
default = ["d", "e", "f"]
}
variable "combined_list" {
value = concat(var.list1, var.list2)
}

  • element():It retrieves an element from a list at a specified index.
locals {
weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
third_weekday = element(local.weekdays, 2)
}
  • slice(): It extracts a sublist from a list, specifying a starting index and an optional ending index.
locals {
numbers = [1, 2, 3, 4, 5]
selected_numbers = slice(local.numbers, 1, 3)
}

  • compact takes a list of strings and returns a new list with any null or empty string elements removed.
> compact(["apple", "", "mango", null, "grape"])
[
"apple",
"mango",
"grape",
]
  • distinct removes duplicates from a list, retaining the first occurrence of each value while preserving their relative ordering.
> distinct(["a", "b", "a", "c", "d", "b"])
[
"a",
"b",
"c",
"d",
]

Terraform map functions

  • merge(): The merge function in Terraform is used to combine multiple maps into a single map.

merge(map1, map2, map3, …) -> merged_map

This function is useful when you want to aggregate configurations from multiple sources. It is mostly used to create flexible and modular configurations.

variable "base_config" {
type = map(string)
default = { key1 = "value1", key2 = "value2" }
}
variable "additional_config" {
type = map(string)
default = { key2 = "new_value2", key3 = "value3" }
}
output "extended_config" {
value = merge(var.base_config, var.additional_config)
}

  • lookup(): The lookup function in Terraform is used to safely access the value of a given key in a map. It returns the value associated with the specified key if it exists or a default value if the key does not exist.

This function is particularly useful in scenarios where you want to dynamically fetch information from a map, especially when dealing with variable configurations and data structures.

variable "environment_vars" {
type = map(string)
default = {
dev = "dev-database-url"
prod = "prod-database-url"
}
}
variable "current_environment" {
type = string
default = "prod"
}
output "database_url" {
value = lookup(var.environment_vars, var.current_environment, "default-database-url")
}

In this example, the lookup function fetches the database URL based on the current environment. If the environment is not found in the map, it defaults to a predefined value.


  • zipmap(): zipmap function in Terraform is used to create a map by pairing elements from two separate lists

This function is particularly useful in scenarios where you have two lists that correspond to each other, and you want to construct a map associating elements from one list with elements from the other

variable "resource_names" {
type = list(string)
default = ["web", "db", "app"]
}

resource "aws_instance" "example" {
count = length(var.resource_names)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = zipmap(["Name"], [var.resource_names[count.index]])
}

In this scenario, the zipmap is used to dynamically assign names to AWS instances based on the elements in the resource_names list.

variable "config_keys" {
type = list(string)
default = ["max_connections", "timeout", "retry_limit"]
}
variable "config_values" {
type = list(number)
default = [100, 30, 5]
}
output "application_config" {
value = zipmap(var.config_keys, var.config_values)
}

Here zipmap is used for configuring application parameters.


  • length(): Returns the length of a list, string, or map.
  • keys(): Returns a list of keys from a map.
  • values(): Returns a list of values from a map.
variable "tags" {
type = map(string)
default = {
"Name" = "my-instance"
"Env" = "production"
"App" = "web"
}
}
locals {
tag_count = length(var.tags)
tag_keys = keys(var.tags)
tag_values = values(var.tags)
}

map(): The map function transforms the values of a map using expressions.

locals {
prices = {
apple = 0.5
banana = 0.3
orange = 0.7
}
discounted_prices = map(
for fruit, price in local.prices : fruit => price * 0.9
)
}

Terraform string Functions

  • format(): format function allows you to create formatted strings by substituting values into placeholders.

In the below example, the format function is used to generate unique names for AWS instances dynamically.

variable "environment" {
type = string
default = "dev"
}
resource "aws_instance" "example" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = format("instance-%s-%02d", var.environment, count.index + 1)
}
}

  • replace(): Replaces occurrences of a specified substring in a given string with another substring.

Syntax replace(string, search, replace)

variable "file_paths" {
type = list(string)
default = ["C:\\Users\\user1\\documents\\file1.txt", "D:\\Data\\file2.txt", "E:\\Projects\\file3.txt"]
}
output "standardized_file_paths" {
value = [for path in var.file_paths : replace(path, "\\", "/")]
}

# We used replace to standardize the file path


  • split(): It splits a string into a list of substrings based on a specified delimiter.

This function is useful in scenarios where you need to extract or manipulate parts of a string within your Terraform configurations.

variable "user_emails" {
type = list(string)
default = ["user1@example.com", "user2@example.com", "user3@example.com"]
}
resource "aws_instance" "user_instances" {
count = length(var.user_emails)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = replace(split("@", var.user_emails[count.index])[0], ".", "-")
}
}

In the above scenario, we have three user email addresses as input, and we’ll use the name part of each email address to create AWS EC2 instances with dynamically generated names.

We used the replace function to replace dots with hyphens in the names.


  • join(): It concatenates a list of strings into a single string, separated by a delimiter.
locals {
join_string = join(",", ["a", "b", "c"])
}
output "join_string" {
value = local.join_string
}
# output will be -> “a, b, c”.

  • title Converts the first letter of each word in the given string to uppercase.
  • upper converts all letters in the given string to uppercase.
  • lower converts all letters in the given string to lowercase.
> title("hello world")
Hello World
> upper("Hello World")
HELLO WORLD
> lower("HELLO WORLD")
hello world
  • chomp trims newline characters at the end of a string.

It is very useful when you are dealing with multiline strings or reading content from a file using filefunction, and want to remove unwanted newline (‘\n’) characters

# reading multiine string
> chomp("This is a multiline string\nwith unnecessary line breaks\n")
This is a multiline string with unnecessary line breaks# reading file
variable "file_path" {
type = string
default = "path/to/file.txt"
}
output "file_content" {
value = chomp(file(var.file_path))
}
  • can and try functions are used to handle situations where a variable may not exist or may be set to null

These functions help make your configurations more robust by providing a way to handle potential errors or missing values.

  • can function checks whether a variable or attribute exists and is non-null.

Returns true if the variable exists and is non-null.
Returns false if the variable does not exist or is explicitly set to null

variable "example_variable" {
type = string
}locals {
variable_exists = can(var.example_variable)
}
  • try function is used to handle situations where a value might be null. It provides a default value if the specified key or attribute is null.

Returns the value of the variable if it’s non-null.
Returns the default value if the variable is explicitly set to null.

variable "example_variable" {
type = string
default = null
}locals {
safe_variable = try(var.example_variable, "default_value")
}

The Terraform file functions

  • abspath function converts a relative file path to an absolute path.
locals {
relative_path = "main.tf"
absolute_path = abspath(local.relative_path)
}
output "absolute_path_output" {
value = local.absolute_path
}
# absolute_path_output = "/Users/akhileshmishra3/terraform-blogs/main.tf"
  • basename function extracts the filename (the last component) from a given path.
locals {
file_path = "/Users/akhileshmishra3/terraform-blogs/main.tf"
file_name = basename(local.file_path)
}
output "file_name_output" {
value = local.file_name
}
# file_name_output = "main.tf"
  • fileexists function checks whether a file exists at the specified path.
variable "file_to_check" {
type = string
default = "files/config.txt"
}
locals {
file_exists = fileexists(var.file_to_check)
}
output "file_exists_output" {
value = local.file_exists
}
# file_exists_output = false
  • file Read the contents of a file at the given path, and return them as a string.
locals {
file_content = file("README.MD")
}
output "file_content_output" {
value = local.file_content
}
# file_content_output = <<-EOT
This file is used for
Terraform functions examples
EOT

This function only works with files that exist before Terraform runs, and It can’t be used with files created dynamically during a Terraform operation.

file is often used to load configuration or data from files into your Terraform configurations.

Suppose we have a fileconfig.txt with the following content

# config.txt
region = "us-west-2"
instance_type = "t2.micro"
ami = "ami-0123456789abcdef0"

You can use the file function to read the contents of this file in your Terraform configuration.

# main.tf
# Read the contents of the 'config.txt' file
variable "config_content" {
type = string
default = file("${path.module}/config.txt")
}
# Parse the configuration content as HCL
locals {
parsed_config = yamldecode(var.config_content)
}
# Use the parsed configuration in your resources
resource "aws_instance" "example" {
ami = local.parsed_config.ami
instance_type = local.parsed_config.instance_type
region = local.parsed_config.region
}

path.module is a built-in variable that represents the directory path where the current Terraform configuration file is located.


Encoding/Decoding Terraform functions

  • yamlencode encode a given value to a string using YAML 1.2 block syntax.

The above example shows how you can use yamlencode Terraform function.

  • base64encode encode a string as a base64-encoded string.

This function is useful when dealing with sensitive information like passwords or access keys.

variable "db_password" {
type = string
sensitive = true
}
resource "kubernetes_secret" "example" {
metadata {
name = "db-credentials"
}
data = {
password = base64encode(var.db_password)
}}

It is often used to encode the custom startup scripts when configuring instances in cloud providers.

variable "user_data" {
type = string
default = "#!/bin/bash\napt-get update\napt-get install -y nginx"
}
resource "aws_instance" "example" {
ami = "ami-12345678"
instance_type = "t2.micro"
user_data = base64encode(var.user_data)
}

Akhilesh Mishra

Akhilesh Mishra

I am Akhilesh Mishra, a self-taught Devops engineer with 11+ years working on private and public cloud (GCP & AWS)technologies.

I also mentor DevOps aspirants in their journey to devops by providing guided learning and Mentorship.

Topmate: https://topmate.io/akhilesh_mishra/