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 [.code]merge[.code] 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

[.code]upper(“hello from env0”)[.code] - the [.code]upper[.code] 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 [.code]upper[.code] function sets [.code]formatted_name[.code] 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 [.code]upper[.code] function is used directly within the resource configuration to set the [.code]Name[.code] 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 [.code]upper[.code] function sets the variable [.code]instance_name[.code] 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 [.code]upper[.code] function call sets the [.code]output[.code] value to "ENV0".

Terraform Function Categories

Functions in Terraform or OpenTofu are organized into several categories.

Category Description Examples
String Manipulate and transform strings join(separator, list), split(separator, string), replace(string, search, replace), trimspace(string) etc.
Numeric Perform arithmetic operations abs(number), ceil(number), floor(number) etc.
Collection Work with lists, maps, and sets length(list or map), element(list, index), flatten(list), merge(map1, map2, ...) etc.
Date and Time Work with date and time values timestamp(), timeadd(timestamp, duration), formatdate(format, timestamp), uuid() etc.
Encoding Encode and decode values, and transform data formats base64encode(string), base64decode(string), jsondecode(string) etc.
Type Conversion Convert between different types tostring(value), tonumber(value), toset(value) etc.
Filesystem Read files from the filesystem file(path), filebase64(path), dirname(path) etc.
IP Network Work with IP addresses and networks cidrnetmask(prefix), cidrrange(prefix) etc.

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 [.code]var.instance_base_name[.code] for the base name of our instance and [.code]var.env[.code] for the environment name in variables in variables.tf

join(separator, list) 

([.code]join[.code]) 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 [.code]join[.code] function to combine [.code]instance_base_name[.code] and [.code]env[.code] variables with a hyphen separator, resulting in "webapp-production".

split(separator, string)

The ([.code]split[.code]) 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 [.code]split[.code] function, which breaks [.code]local.joined_name[.code] (e.g., "webapp-production") into a list of substrings: ["webapp", "production"].

replace(string, substr, replacement)

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

locals {
  replaced_name = replace(local.joined_name, "webapp", "service")
}

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

trimspace(string)

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

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

The [.code]trimspace[.code] 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 [.code]var.desired_cpu[.code] for CPU allocation and [.code]var.desired_disk_size[.code] for disk size in variables.tf.

abs(number)

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

locals {
  disk_abs_size = tostring(abs(var.desired_disk_size))
}

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

ceil(number)

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

locals {
  cpu_ceiled = tostring(ceil(var.desired_cpu))
}

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

floor(number)

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

locals {
  cpu_floored = tostring(floor(var.desired_cpu))
}

Here, the ([.code]floor[.code]) function rounds down the  [.code]desired_cpu[.code] 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 [.code]var.security_groups[.code] to list all the security groups and [.code]var.additional_tags[.code] for adding additional tags to the resource in variables.tf.

length(list)

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

locals {
  sg_length = length(var.security_groups)
}

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

element(list, index)

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

locals {
  sg_element = element(var.security_groups, 0)
}

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

flatten(list)

The ([.code]flatten[.code]) 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 ([.code]flatten[.code]) function combines nested lists into a single list, resulting in ["env:production", "app:web", "tier:frontend", "region:us-east-2"]

merge(map1, map2, ...)

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

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

In this example, the ([.code]merge[.code]) 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 [.code]timestamp[.code] function name returns UTC's current date and time.

locals {
  created_At = timestamp()
}
output "current_time" {
  value = local.created_At
}

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

timeadd(timestamp, duration)

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

locals {
  new_timestamp = timeadd(timestamp(), "168h")
}

The [.code]timeadd[.code] function adds 168 hours (7 days) to the current timestamp to set the [.code]Backup_Schedule[.code] tag and the [.code]backup_time[.code] 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 [.code]var.config_json[.code] for the configurations in JSON format in variables.tf.

base64encode(string)

The [.code]base64encode[.code] function encodes a string to Base64 format.

locals {
encoded_string = base64encode(local.original_string)
}

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

jsondecode(string)

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

locals {
 decoded_config = jsondecode(var.config_json)
}

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

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 [.code]var.condition[.code] 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 [.code]element[.code] function retrieves an item from a list by index.

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

Here, the [.code]element[.code] function retrieves the second item (index 1) from [.code]var.instance_names[.code], 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 [.code]aws_instance.env0[.code] 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 [.code]count[.code], [.code]for_each[.code], and [.code]for[.code] let developers create and manage resources automatically.

for loop

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

Let us take an example where the [.code]for[.code] 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 [.code]for_each[.code] 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, [.code]for_each[.code] creates multiple AWS instances based on the server types defined in the [.code]servers[.code] 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 [.code]required_providers[.code] 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 [.code]corefunc[.code] provider is specified and pinned to version [.code]1.4.0[.code]. The provider is then initialized without any additional configuration. The [.code]str_camel[.code] 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 [.code]timestamp()[.code], [.code]concat()[.code], and [.code]lookup()[.code].

Q. What does [.code]${}[.code] mean in Terraform?

[.code]${}[.code] is used for interpolation in Terraform. It allows you to embed expressions within strings to reference variables, resource attributes, and call functions. For example: [.code]${var.instance_id}[.code] retrieves the value of [.code]instance_id[.code] from the [.code]var[.code] 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 [.code]contains()[.code] 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.

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 [.code]merge[.code] 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

[.code]upper(“hello from env0”)[.code] - the [.code]upper[.code] 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 [.code]upper[.code] function sets [.code]formatted_name[.code] 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 [.code]upper[.code] function is used directly within the resource configuration to set the [.code]Name[.code] 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 [.code]upper[.code] function sets the variable [.code]instance_name[.code] 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 [.code]upper[.code] function call sets the [.code]output[.code] value to "ENV0".

Terraform Function Categories

Functions in Terraform or OpenTofu are organized into several categories.

Category Description Examples
String Manipulate and transform strings join(separator, list), split(separator, string), replace(string, search, replace), trimspace(string) etc.
Numeric Perform arithmetic operations abs(number), ceil(number), floor(number) etc.
Collection Work with lists, maps, and sets length(list or map), element(list, index), flatten(list), merge(map1, map2, ...) etc.
Date and Time Work with date and time values timestamp(), timeadd(timestamp, duration), formatdate(format, timestamp), uuid() etc.
Encoding Encode and decode values, and transform data formats base64encode(string), base64decode(string), jsondecode(string) etc.
Type Conversion Convert between different types tostring(value), tonumber(value), toset(value) etc.
Filesystem Read files from the filesystem file(path), filebase64(path), dirname(path) etc.
IP Network Work with IP addresses and networks cidrnetmask(prefix), cidrrange(prefix) etc.

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 [.code]var.instance_base_name[.code] for the base name of our instance and [.code]var.env[.code] for the environment name in variables in variables.tf

join(separator, list) 

([.code]join[.code]) 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 [.code]join[.code] function to combine [.code]instance_base_name[.code] and [.code]env[.code] variables with a hyphen separator, resulting in "webapp-production".

split(separator, string)

The ([.code]split[.code]) 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 [.code]split[.code] function, which breaks [.code]local.joined_name[.code] (e.g., "webapp-production") into a list of substrings: ["webapp", "production"].

replace(string, substr, replacement)

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

locals {
  replaced_name = replace(local.joined_name, "webapp", "service")
}

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

trimspace(string)

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

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

The [.code]trimspace[.code] 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 [.code]var.desired_cpu[.code] for CPU allocation and [.code]var.desired_disk_size[.code] for disk size in variables.tf.

abs(number)

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

locals {
  disk_abs_size = tostring(abs(var.desired_disk_size))
}

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

ceil(number)

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

locals {
  cpu_ceiled = tostring(ceil(var.desired_cpu))
}

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

floor(number)

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

locals {
  cpu_floored = tostring(floor(var.desired_cpu))
}

Here, the ([.code]floor[.code]) function rounds down the  [.code]desired_cpu[.code] 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 [.code]var.security_groups[.code] to list all the security groups and [.code]var.additional_tags[.code] for adding additional tags to the resource in variables.tf.

length(list)

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

locals {
  sg_length = length(var.security_groups)
}

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

element(list, index)

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

locals {
  sg_element = element(var.security_groups, 0)
}

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

flatten(list)

The ([.code]flatten[.code]) 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 ([.code]flatten[.code]) function combines nested lists into a single list, resulting in ["env:production", "app:web", "tier:frontend", "region:us-east-2"]

merge(map1, map2, ...)

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

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

In this example, the ([.code]merge[.code]) 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 [.code]timestamp[.code] function name returns UTC's current date and time.

locals {
  created_At = timestamp()
}
output "current_time" {
  value = local.created_At
}

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

timeadd(timestamp, duration)

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

locals {
  new_timestamp = timeadd(timestamp(), "168h")
}

The [.code]timeadd[.code] function adds 168 hours (7 days) to the current timestamp to set the [.code]Backup_Schedule[.code] tag and the [.code]backup_time[.code] 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 [.code]var.config_json[.code] for the configurations in JSON format in variables.tf.

base64encode(string)

The [.code]base64encode[.code] function encodes a string to Base64 format.

locals {
encoded_string = base64encode(local.original_string)
}

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

jsondecode(string)

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

locals {
 decoded_config = jsondecode(var.config_json)
}

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

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 [.code]var.condition[.code] 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 [.code]element[.code] function retrieves an item from a list by index.

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

Here, the [.code]element[.code] function retrieves the second item (index 1) from [.code]var.instance_names[.code], 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 [.code]aws_instance.env0[.code] 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 [.code]count[.code], [.code]for_each[.code], and [.code]for[.code] let developers create and manage resources automatically.

for loop

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

Let us take an example where the [.code]for[.code] 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 [.code]for_each[.code] 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, [.code]for_each[.code] creates multiple AWS instances based on the server types defined in the [.code]servers[.code] 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 [.code]required_providers[.code] 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 [.code]corefunc[.code] provider is specified and pinned to version [.code]1.4.0[.code]. The provider is then initialized without any additional configuration. The [.code]str_camel[.code] 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 [.code]timestamp()[.code], [.code]concat()[.code], and [.code]lookup()[.code].

Q. What does [.code]${}[.code] mean in Terraform?

[.code]${}[.code] is used for interpolation in Terraform. It allows you to embed expressions within strings to reference variables, resource attributes, and call functions. For example: [.code]${var.instance_id}[.code] retrieves the value of [.code]instance_id[.code] from the [.code]var[.code] 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 [.code]contains()[.code] 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.

Logo Podcast
With special guest
John Willis

Schedule a technical demo. See env0 in action.

Footer Illustration