DEV Community

Cover image for Terraform Functions Guide: Complete List with Detailed Examples
env0 Team for env0

Posted on • Originally published at env0.com

Terraform Functions Guide: Complete List with Detailed Examples

Terraform functions are essential for creating effective infrastructure code. They help automate tasks like generating resource names, calculating values, and managing data structures. 

In this blog post, we will explore using Terraform CLI's built-in functions in different ways, such as in locals, the console, output, and variables.

Understanding these functions is important for any DevOps or Infrastructure engineer who wants to improve their Infrastructure as Code (IaC) skills.

Disclaimer

‍_All Terraform functions discussed here work similarly in OpenTofu, the open-source Terraform alternative. However, in order to keep it simple and closer to what devops engineers are familiar with, we will refer to them as Terraform functions.

What are Terraform Functions

Terraform functions are built-in features that help with simple management and manipulation of data within your Terraform configurations, enabling you to perform data transformations and ensure smooth infrastructure provisioning. 

Terraform's built-in functions include a variety of utilities to transform and combine values such as string formatting, arithmetic calculations, and working with lists and maps directly in your code.

Use Cases for Terraform Functions

Terraform functions are important for tasks such as variable interpolation, generating resource names, and applying conditional logic. Let us discuss a few of the use cases below.

  • Concatenating Strings - You can generate unique resource names by appending environment names (e.g., "dev", "prod") to base names (e.g., "app-server"), resulting in names like "app-server-dev" and "app-server-prod".
  • Splitting Strings - You can split a comma-separated variable like "key1,value1,key2,value2" into a list of individual items: ["key1", "value1", "key2", "value2"] using the split function name.
  • Converting Data Types - You can convert a list of IP addresses to a set to remove duplicates and ensure each IP address is unique. Terraform's built-in functions support such data-type transformations.
  • Merging Tags - You can combine tags from various resources into a single set using the merge function. This helps manage and apply consistent tagging across resources.
  • Implementing Conditional Logic - You can set different instance types based on a variable, such as t2.micro for development and t2.large for production.
  • Generating Timestamps - You can record the exact time of resource creation for auditing or tracking purposes.

Testing Functions with Terraform Console

Before you apply functions in your configuration, the Terraform console helps you test and try the functions in a CLI. It shows how functions behave with different inputs in real-time, allowing you to fix issues immediately. 

Here's how you can get started with the Terraform console:

Open your Bash or any command-line interface:

By using the Terraform console, you can quickly grasp the functionality of various Terraform functions and integrate them into your Terraform or OpenTofu configuration.

Basic Structure and Usage 

Functions in Terraform are used within expressions to perform various operations. The basic structure involves calling the function by name and passing the required arguments.

For example, the

upper(“hello from env0”) - the upper function converts the string to uppercase:

You can use functions in your configuration in various ways. Let us take a look at some of them. 

Locals

While working with Terraform locals, you can make use of functions to keep your configuration DRY (Don't Repeat Yourself), which makes it easier to manage and update values in one place. 

For example:

locals {
  formatted_name = upper(“env0”)
}
Enter fullscreen mode Exit fullscreen mode

Here, the upper function sets formatted_name to "ENV0".

Resource Configuration

Functions can also be used directly within your resource configurations to set values dynamically.

For example:

resource “aws_instance” “env0” {
  ami           = “ami-09040d770ffe2224f”
  instance_type = “t2.micro”
  tags = {
    Name = upper(“env0”)
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, the upper function is used directly within the resource configuration to set the Name tag.

Variables

You can use functions within Terraform variables to set values based on other inputs dynamically. This flexibility allows you to transform and combine values as needed.

For example:

variable “instance_name” {
  default = upper(“env0”)
}
Enter fullscreen mode Exit fullscreen mode

Here, the upper function sets the variable instance_name default value to "ENV0”.

Outputs

You can also use functions in the output block to display the expression results.

For example:

output “formatted_name” {
  value = upper(“env0”)
}
Enter fullscreen mode Exit fullscreen mode

Here, the upper function call sets the output value to "ENV0".

Terraform Function Categories

Functions in Terraform or OpenTofu are organized into several categories.

Terraform Function Categories

String 

This category focuses on string-related functions, making it easier to construct and manipulate strings within your code. This can be particularly useful for naming resources, generating tags, and formatting output values.

For example: let us define var.instance_base_name for the base name of our instance and var.env for the environment name in variables in variables.tf

join(separator, list) 

(join) function concatenates a list of strings into a single string using a specified separator.

locals {
joined_name= join("-", [var.instance_base_name, var.env])
}
Enter fullscreen mode Exit fullscreen mode

To create an aws_instance resource name, we use the join function to combine instance_base_name and env variables with a hyphen separator, resulting in "webapp-production".

split(separator, string)

The (split) function splits a string into a list of substrings using a specified separator.

locals {
 split_name = split("-", local.joined_name)
}
Enter fullscreen mode Exit fullscreen mode

To split the name, we use the split function, which breaks local.joined_name (e.g., "webapp-production") into a list of substrings: ["webapp", "production"].

replace(string, substr, replacement)

The (replace) function replaces all occurrences of substr within string with replacement.

locals {
  replaced_name = replace(local.joined_name, "webapp", "service")
}
Enter fullscreen mode Exit fullscreen mode

Here, the replace function changes the given string from "webapp-prodution" to "service-production" by replacing "webapp" with "service".

trimspace(string)

The (trimspace) function removes leading and trailing spaces from a string.

locals {
  trimmed_description = trimspace("   This is a description with leading and trailing spaces   ")
}
Enter fullscreen mode Exit fullscreen mode

The trimspace function removes the leading and trailing spaces from the description, resulting in "This is a description with leading and trailing spaces".

Now, we will create a resource block using the locals from above,

Numeric 

Numeric-related functions help execute calculations on numeric values, such as rounding numbers or getting absolute values. These are helpful when adjusting resource configurations based on numeric input, such as sizing resources or calculating derived values.

For example: define var.desired_cpu for CPU allocation and var.desired_disk_size for disk size in variables.tf.

abs(number)

The (abs) function returns the absolute value of a given number.

locals {
  disk_abs_size = tostring(abs(var.desired_disk_size))
}
Enter fullscreen mode Exit fullscreen mode

Here, the (abs) function converts the desired_disk_size from -100 to its absolute value, 100.

ceil(number)

The (ceil) function rounds a number up to the nearest whole number.

locals {
  cpu_ceiled = tostring(ceil(var.desired_cpu))
}
Enter fullscreen mode Exit fullscreen mode

Here, the (ceil) function rounds the desired_cpu from 3.7 up to 4.

floor(number)

The (floor) function rounds a number down to the nearest whole number.

locals {
  cpu_floored = tostring(floor(var.desired_cpu))
}
Enter fullscreen mode Exit fullscreen mode

Here, the (floor) function rounds down the desired_cpu from 3.7 to 3.

These calculated values are used to define tags and configure an AWS instance's root block device. 

Now, we will create a resource block using the locals from above:

Collection 

This category focuses on handling and manipulating lists and maps, making working with complex data structures in your configurations easier. These functions are useful for counting elements, retrieving specific items, flattening nested lists, and merging maps.

For example: define var.security_groups to list all the security groups and var.additional_tags for adding additional tags to the resource in variables.tf.

length(list)

The (length) function returns the number of elements in a list.

locals {
  sg_length = length(var.security_groups)
}
Enter fullscreen mode Exit fullscreen mode

Here, the (length) function counts the number of items within the security_groups variable, defining the total number of security groups.

element(list, index)

The (element) function retrieves a single element from a list by its index.

locals {
  sg_element = element(var.security_groups, 0)
}
Enter fullscreen mode Exit fullscreen mode

The (element) function retrieves the first item from the security_groups list, returning the first defined security group.

flatten(list)

The (flatten) function collapses a multi-dimensional list into a single-dimensional list.

locals {
  flat_list = flatten([
    ["env:production", "app:web"],
    ["tier:frontend", "region:us-east-2"]
  ])
}
Enter fullscreen mode Exit fullscreen mode

The (flatten) function combines nested lists into a single list, resulting in ["env:production", "app:web", "tier:frontend", "region:us-east-2"]

merge(map1, map2, ...)

The (merge) function combines multiple maps into a single map.

locals {
  merged_tags = merge(
    {
      "Environment" = "production"
      "Project"     = "env0"
    },
    var.additional_tags
  )
}
Enter fullscreen mode Exit fullscreen mode

In this example, the (merge) function combines the default tags with additional tags.

Now, we will create a resource block using the locals from above:

Date and Time 

This category focuses on date and time functions, allowing you to work with timestamps and schedule events. These functions are helpful for tasks like setting creation timestamps, scheduling backups, or calculating expiration dates.

timestamp()

The timestamp function name returns UTC's current date and time.

locals {
  created_At = timestamp()
}
output "current_time" {
  value = local.created_At
}
Enter fullscreen mode Exit fullscreen mode

The timestamp function captures the current date and time when the configuration is applied and makes this timestamp available as both a local variable created_At and an output current_time.

timeadd(timestamp, duration)

The timeadd function adds a duration to a timestamp, returning a new timestamp.

locals {
  new_timestamp = timeadd(timestamp(), "168h")
}
Enter fullscreen mode Exit fullscreen mode

The timeadd function adds 168 hours (7 days) to the current timestamp to set the Backup_Schedule tag and the backup_time output.

Now, we will create a resource block using the locals from above:

Encoding 

This category focuses on encoding and decoding functions that help transform data between different formats, such as encoding strings to Base64 or decoding JSON strings into maps. These functions are useful for handling data in specific formats required by APIs or other services.

For example: define var.config_json for the configurations in JSON format in variables.tf.

base64encode(string)

The base64encode function encodes a string to Base64 format.

locals {
encoded_string = base64encode(local.original_string)
}
Enter fullscreen mode Exit fullscreen mode

The base64encode function encodes the original_string "This is a sample string." into Base64 format, resulting in encoded_string.

jsondecode(string)

The jsondecode function decodes a JSON string into a map or list.

locals {
 decoded_config = jsondecode(var.config_json)
}
Enter fullscreen mode Exit fullscreen mode

The jsondecode function decodes the JSON string stored in config_json into a map, resulting in decoded_config.

Now, we will create a resource block using the locals from above:

For the complete code for all categories, please refer to this repository.

Working with Expressions

Expressions in Terraform help you handle and evaluate values in your configuration. Using conditional expressions, splat syntax, and functions to work with lists and maps can make your configurations more straightforward. 

These methods let you create resources based on conditions, manage collections, and retrieve specific values from lists and maps.

This approach simplifies the setup and ensures your infrastructure meets specific requirements.

Let's look at an example where we can use conditional expressions, splat syntax, and functions to manipulate data in our Terraform configurations.

Conditional Execution Using Ternary Operator

The ternary operator lets you choose between two values based on a condition.

locals {
 condition_result = var.condition ? upper("SUCCESS") : lower("FAILURE")
}
Enter fullscreen mode Exit fullscreen mode

If var.condition is true, the result is "SUCCESS" in uppercase; otherwise, it is "FAILURE" in lowercase. This helps dynamically set values based on conditions.

Accessing List Items With element

The element function retrieves an item from a list by index.

locals { specific_instance_name = element(var.instance_names, 1) }

Here, the element function retrieves the second item (index 1) from var.instance_names, which is "instance2". This helps you select specific items from a list.

Splat Syntax 

The splat syntax ([*]) allows you to access a specific attribute from all elements in a list of resources.

locals {
 instance_ids = aws_instance.env0[*].id
}
Enter fullscreen mode Exit fullscreen mode

Here, the function retrieves the IDs of all instances created by the aws_instance.env0 resource. This is useful for collecting all IDs and putting them into a list.

Joining Instance IDs into a Single String

The join function concatenates a list of strings into a single string with a specified separator.

locals {
 joined_instance_ids = join(",", local.instance_ids)
}
Enter fullscreen mode Exit fullscreen mode

Now, we will create a resource block using the locals from above:

In this example, the function combines all instance IDs into a single string, separated by commas. This is helpful for formatting lists into strings.

For the full code, refer to this GitHub repository.

Looping in Terraform

So far, we have learned the built-in functions in Terraform. However, there will be times when you’ll need to iterate over functions to solve slightly more complex problems by making use of looping mechanisms. 

Functions like count, for_each, and for let developers create and manage resources automatically.

for loop

The for loop in Terraform allows you to iterate over collections and transform their data. 

Let us take an example where the for loop transforms each tag key to uppercase and each tag value to lowercase, demonstrating how to iterate over a map and apply transformations.

tags = { for key, value in var.tags : upper(key) => lower(value) }
Enter fullscreen mode Exit fullscreen mode

for_each

The for_each construct allows you to create multiple instances of a resource based on the items in a map or set. This is useful for managing collections of resources with similar properties but unique values. 

In this example, for_each creates multiple AWS instances based on the server types defined in the servers variable.

count

The count meta-argument allows you to conditionally create resources based on a boolean expression. This is useful for managing resources that should only be created under specific conditions, such as deploying additional infrastructure for a staging environment.

count = var.create_extra_instance ? 1 : 0
Enter fullscreen mode Exit fullscreen mode

For the full code, refer to this GitHub repository.

OpenTofu provider functions with env0

Until now, we've only discussed the shared functions of Terraform and OpenTofu. Now, let's look at OpenTofu provider functions, which add a unique extra capabilities by allowing providers to register and create custom functions.

When Terraform processes the required_providers block, OpenTofu asks each provider if they have any custom functions to add. These functions are then available in your module using the format :

provider::<provider_name>::<function_name> 
Enter fullscreen mode Exit fullscreen mode

And you can also use aliases for providers. 

Note that these functions are only available in the module where the provider is defined and are not shared with child modules.

Let's take an example using the following OpenTofu code to demonstrate how to use provider functions:

terraform {
  required_providers {
    corefunc = {
      source = "northwood-labs/corefunc"
      version = "1.4.0"
    }
  }
}
provider "corefunc" {
}
output "test_with_number" {
  value = provider::corefunc::str_camel("test with number -123.456")
}
Enter fullscreen mode Exit fullscreen mode

In this configuration, the corefunc provider is specified and pinned to version 1.4.0. The provider is then initialized without any additional configuration. The str_camel function from the corefunc provider converts a string to kebab-case, removing any non-alphanumeric characters. 

You can use various functions from the corefunc provider, which you can find in corefunc functions documentation.

Next, let's use env0 to run this OpenTofu configuration. env0 is a powerful tool for automating and managing Terraform deployments, making running and managing your IaC easier.

Here's an overview of the steps to follow:

  1. Create a new project in env0 and connect it to the repository containing the OpenTofu configuration.

  1. env0 will automatically trigger the deployment, execute the OpenTofu code, and produce the desired output.

Using env0 to manage Terraform or OpenTofu deployments streamlines the process, allowing more focus on development and less on deployment.

Conclusion

We've covered how Terraform functions can simplify your infrastructure configurations. These functions enable you to create maintainable code by handling tasks like string manipulations, calculations, and data transformations.

Testing functions with the Terraform console ensure they work as expected before integrating them into your configurations.

Additionally, using loops and exploring OpenTofu provider functions with env0 workflow can further enhance your infrastructure management.

Frequently Asked Questions 

Q. What is the key function in Terraform?

In Terraform, a key function is any built-in function that performs a specific operation, such as generating timestamps, manipulating strings, or working with data types. Examples include timestamp(), concat(), and lookup().

Q. What does ${} mean in Terraform?

${} is used for interpolation in Terraform. It allows you to embed expressions within strings to reference variables, resource attributes, and call functions. For example: ${var.instance_id} retrieves the value of instance_id from the var object.

Q. How do you check if a string contains a substring in Terraform?

To check if a string contains a substring, you can use the contains() function within a conditional expression:

locals {
   str = "Hello, env0!"
   substr = "Terraform"
   has_substr = contains(local.str, local.substr)
}
Enter fullscreen mode Exit fullscreen mode

Q. Can I create functions in Terraform?

No, you cannot create custom functions in Terraform. However, you can use existing built-in functions and modules to encapsulate reusable code.

Top comments (0)