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”)
}
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”)
}
}
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”)
}
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”)
}
Here, the upper
function call sets the output
value to "ENV0".
Terraform Function Categories
Functions in Terraform or OpenTofu are organized into several 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])
}
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)
}
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")
}
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 ")
}
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))
}
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))
}
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))
}
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)
}
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)
}
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"]
])
}
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
)
}
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
}
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")
}
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)
}
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)
}
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")
}
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
}
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)
}
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) }
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
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>
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")
}
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:
- Create a new project in env0 and connect it to the repository containing the OpenTofu configuration.
- 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)
}
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)