Terraform Functions: Your Code’s Swiss Army Knife – T7
Hey there! Welcome back to our Terraform adventure. Today we’re going to learn about Terraform functions – built-in tools that help you do amazing things with your data.
Think of functions like a Swiss Army knife for your code.
Need to make text uppercase? There’s a function for that.
Want to combine two lists? There’s a function for that too. Need to do some math? Yep, functions to the rescue!
What Are Functions?
Functions are like little helpers that take some input, do something useful with it, and give you back a result.
Here’s the basic pattern:
result = function_name(input)
Let’s see a super simple example:
variable "my_name" {
type = string
default = "john smith"
}
locals {
clean_name = upper(var.my_name)
}
output "formatted_name" {
value = local.clean_name
}
What happened?
upper()
is a function that makes text uppercase- Input: “john smith”
- Output: “JOHN SMITH”
Pretty simple, right?
String Functions: Working with Text
Let’s start with functions that help you work with text.
Making Text Look Nice
variable "user_input" {
type = string
default = " Hello World "
}
locals {
# Remove extra spaces
clean_text = trim(var.user_input, " ")
# Make it uppercase
upper_text = upper(local.clean_text)
# Make it lowercase
lower_text = lower(local.clean_text)
# Make first letter uppercase
title_text = title(local.clean_text)
}
output "text_examples" {
value = {
original = var.user_input
clean = local.clean_text
upper = local.upper_text
lower = local.lower_text
title = local.title_text
}
}
This shows:
trim()
removes spaces from the beginning and endupper()
makes everything UPPERCASElower()
makes everything lowercasetitle()
Makes The First Letter Of Each Word Uppercase
Finding and Replacing Text
variable "server_name" {
type = string
default = "my-awesome-server"
}
locals {
# Replace dashes with underscores
underscore_name = replace(var.server_name, "-", "_")
# Replace multiple things
clean_name = replace(replace(var.server_name, "-", "_"), "awesome", "super")
}
resource "local_file" "server_config" {
filename = "${local.underscore_name}.conf"
content = "Server name: ${local.clean_name}"
}
output "name_changes" {
value = {
original = var.server_name
underscore = local.underscore_name
clean = local.clean_name
}
}
What replace()
does:
replace(text, old, new)
finds “old” text and replaces it with “new” text- You can chain multiple
replace()
functions together
Combining Text
variable "first_name" {
type = string
default = "John"
}
variable "last_name" {
type = string
default = "Doe"
}
variable "company" {
type = string
default = "TechCorp"
}
locals {
# Simple joining
full_name = "${var.first_name} ${var.last_name}"
# Using format function (like printf)
email = format("%s.%s@%s.com", lower(var.first_name), lower(var.last_name), lower(var.company))
# Join with specific separator
name_parts = join("-", [var.first_name, var.last_name, var.company])
}
output "name_formatting" {
value = {
full_name = local.full_name
email = local.email
name_parts = local.name_parts
}
}
New functions:
format()
is like a template –%s
gets replaced with the valuesjoin()
combines a list with a separator between items
List Functions: Working with Lists
Lists are super useful, and Terraform has lots of functions to work with them.
Basic List Operations
variable "fruits" {
type = list(string)
default = ["apple", "banana", "cherry"]
}
variable "vegetables" {
type = list(string)
default = ["carrot", "broccoli"]
}
locals {
# How many items?
fruit_count = length(var.fruits)
# Combine lists
all_food = concat(var.fruits, var.vegetables)
# Check if something exists
has_apple = contains(var.fruits, "apple")
has_pizza = contains(var.fruits, "pizza")
# Get specific items
first_fruit = var.fruits[0]
last_fruit = var.fruits[length(var.fruits) - 1]
}
output "list_operations" {
value = {
fruit_count = local.fruit_count
all_food = local.all_food
has_apple = local.has_apple
has_pizza = local.has_pizza
first_fruit = local.first_fruit
last_fruit = local.last_fruit
}
}
What these functions do:
length()
counts items in a listconcat()
combines multiple lists into onecontains()
checks if an item exists in a list (true/false)
Sorting and Organizing Lists
variable "server_names" {
type = list(string)
default = ["web-03", "api-01", "db-02", "web-01", "api-02"]
}
locals {
# Sort alphabetically
sorted_servers = sort(var.server_names)
# Reverse the order
reversed_servers = reverse(local.sorted_servers)
# Remove duplicates (if any)
unique_servers = distinct(var.server_names)
# Get just part of the list
first_three = slice(local.sorted_servers, 0, 3)
}
output "list_sorting" {
value = {
original = var.server_names
sorted = local.sorted_servers
reversed = local.reversed_servers
unique = local.unique_servers
first_three = local.first_three
}
}
New functions:
sort()
puts items in alphabetical orderreverse()
flips the orderdistinct()
removes duplicate itemsslice(list, start, end)
gets items from position start to end
Map Functions: Working with Key-Value Pairs
Maps are like dictionaries – they have keys and values. Here’s how to work with them:
variable "server_config" {
type = map(string)
default = {
web = "t2.micro"
database = "t2.small"
cache = "t2.nano"
}
}
variable "additional_config" {
type = map(string)
default = {
backup = "t2.micro"
monitor = "t2.nano"
}
}
locals {
# Get all the keys
server_types = keys(var.server_config)
# Get all the values
instance_sizes = values(var.server_config)
# Combine maps
all_servers = merge(var.server_config, var.additional_config)
# Check if a key exists
has_web_server = contains(keys(var.server_config), "web")
}
output "map_operations" {
value = {
server_types = local.server_types
instance_sizes = local.instance_sizes
all_servers = local.all_servers
has_web_server = local.has_web_server
}
}
Map functions:
keys()
gets all the keys (left side)values()
gets all the values (right side)merge()
combines multiple maps- Use
contains(keys(map), "key_name")
to check if a key exists
Math Functions: Doing Calculations
Sometimes you need to do math in your Terraform code:
variable "server_count" {
type = number
default = 7
}
variable "cpu_per_server" {
type = number
default = 2.5
}
variable "monthly_cost" {
type = number
default = 45.99
}
locals {
# Round numbers
rounded_cpu = ceil(var.cpu_per_server) # Round up to 3
floored_cpu = floor(var.cpu_per_server) # Round down to 2
# Find min and max
min_servers = min(var.server_count, 5)
max_servers = max(var.server_count, 10)
# Calculate total costs
total_cpu = var.server_count * var.cpu_per_server
yearly_cost = var.monthly_cost * 12
# Get absolute value (always positive)
difference = abs(var.server_count - 10)
}
output "math_examples" {
value = {
original_cpu = var.cpu_per_server
rounded_up = local.rounded_cpu
rounded_down = local.floored_cpu
min_servers = local.min_servers
max_servers = local.max_servers
total_cpu = local.total_cpu
yearly_cost = local.yearly_cost
difference = local.difference
}
}
Math functions:
ceil()
rounds up to the next whole numberfloor()
rounds down to the previous whole numbermin()
finds the smallest numbermax()
finds the largest numberabs()
makes negative numbers positive
Type Conversion Functions: Changing Data Types
Sometimes you need to convert between different types of data:
variable "port_number" {
type = string
default = "8080"
}
variable "server_count" {
type = number
default = 3
}
variable "feature_flags" {
type = list(string)
default = ["ssl", "backup", "monitoring"]
}
locals {
# Convert string to number
port_as_number = tonumber(var.port_number)
# Convert number to string
count_as_string = tostring(var.server_count)
# Convert list to set
features_set = toset(var.feature_flags)
# Convert list to map (with indices as keys)
features_map = {
for i, feature in var.feature_flags : i => feature
}
}
output "type_conversions" {
value = {
port_number = var.port_number
port_as_number = local.port_as_number
count_as_string = local.count_as_string
features_list = var.feature_flags
features_set = local.features_set
features_map = local.features_map
}
}
Conversion functions:
tonumber()
converts text to numbertostring()
converts number to texttoset()
converts list to set (removes duplicates)
Date and Time Functions
Need to work with dates and times? Terraform has you covered:
locals {
# Get current time
now = timestamp()
# Format the current time
readable_time = formatdate("YYYY-MM-DD hh:mm:ss", timestamp())
# Just the date
today = formatdate("YYYY-MM-DD", timestamp())
# Custom format
custom_format = formatdate("DD/MM/YYYY at hh:mm", timestamp())
}
resource "local_file" "timestamp_info" {
filename = "created-${formatdate("YYYY-MM-DD", timestamp())}.txt"
content = <<-EOF
This file was created on ${local.readable_time}
Today's date: ${local.today}
Custom format: ${local.custom_format}
Raw timestamp: ${local.now}
EOF
}
output "time_examples" {
value = {
now = local.now
readable = local.readable_time
today = local.today
custom_format = local.custom_format
}
}
Time functions:
timestamp()
gets the current date and timeformatdate(format, time)
formats time in a readable way- Common formats: YYYY (year), MM (month), DD (day), hh (hour), mm (minute), ss (second)
File Functions: Working with Files
You can read files and use them in your Terraform:
# First, let's create a file to read
resource "local_file" "sample_data" {
filename = "data.txt"
content = "Hello from file!"
}
# Create a JSON file
resource "local_file" "config_json" {
filename = "config.json"
content = jsonencode({
app_name = "MyApp"
version = "1.0"
features = ["auth", "api", "web"]
})
}
locals {
# Read file content
file_content = file("data.txt")
# Read and parse JSON
config_data = jsondecode(file("config.json"))
# Base64 encode content
encoded_content = base64encode("Secret message!")
# Decode it back
decoded_content = base64decode(local.encoded_content)
}
output "file_operations" {
value = {
file_content = local.file_content
app_name = local.config_data.app_name
features = local.config_data.features
encoded = local.encoded_content
decoded = local.decoded_content
}
}
File functions:
file()
reads the content of a filejsondecode()
converts JSON text to Terraform datajsonencode()
converts Terraform data to JSON textbase64encode()
andbase64decode()
work with encoded data
Practical Example: Smart Resource Naming
Let’s combine multiple functions to create a smart naming system:
variable "app_config" {
type = object({
name = string
environment = string
region = string
team = string
version = string
})
default = {
name = "My Awesome App"
environment = "production"
region = "us-west-2"
team = "Platform Team"
version = "v2.1.3"
}
}
locals {
# Clean and format the app name
clean_app_name = lower(replace(replace(var.app_config.name, " ", "-"), "_", "-"))
# Create consistent naming
resource_prefix = join("-", [
local.clean_app_name,
var.app_config.environment,
replace(var.app_config.region, "-", "")
])
# Extract version parts
version_parts = split(".", replace(var.app_config.version, "v", ""))
major_version = local.version_parts[0]
minor_version = local.version_parts[1]
# Create tags
common_tags = merge(
{
Name = local.resource_prefix
Environment = title(var.app_config.environment)
Region = upper(var.app_config.region)
Team = var.app_config.team
Version = var.app_config.version
MajorVer = local.major_version
CreatedDate = formatdate("YYYY-MM-DD", timestamp())
},
var.app_config.environment == "production" ? {
CriticalService = "true"
BackupRequired = "true"
} : {}
)
# Generate different resource names
bucket_name = "${local.resource_prefix}-storage"
database_name = replace(local.resource_prefix, "-", "_")
server_name = "${local.resource_prefix}-server"
# Create a summary
summary = format(
"App: %s | Env: %s | Region: %s | Version: %s.%s",
title(local.clean_app_name),
upper(var.app_config.environment),
upper(var.app_config.region),
local.major_version,
local.minor_version
)
}
# Create configuration files
resource "local_file" "app_config" {
filename = "${local.resource_prefix}-config.json"
content = jsonencode({
application = {
name = var.app_config.name
clean_name = local.clean_app_name
environment = var.app_config.environment
region = var.app_config.region
version = var.app_config.version
major_version = tonumber(local.major_version)
minor_version = tonumber(local.minor_version)
}
resources = {
prefix = local.resource_prefix
bucket = local.bucket_name
database = local.database_name
server = local.server_name
}
tags = local.common_tags
summary = local.summary
})
}
resource "local_file" "deployment_script" {
filename = "deploy-${local.resource_prefix}.sh"
content = <<-EOF
#!/bin/bash
# Deployment script for ${var.app_config.name}
# Generated on ${formatdate("YYYY-MM-DD hh:mm:ss", timestamp())}
echo "Deploying ${local.summary}"
echo "Creating bucket: ${local.bucket_name}"
echo "Setting up database: ${local.database_name}"
echo "Starting server: ${local.server_name}"
# Apply tags
%{for key, value in local.common_tags}
echo "Tag: ${key}=${value}"
%{endfor}
echo "Deployment complete!"
EOF
}
output "smart_naming_results" {
value = {
original_name = var.app_config.name
clean_name = local.clean_app_name
resource_prefix = local.resource_prefix
bucket_name = local.bucket_name
database_name = local.database_name
server_name = local.server_name
major_version = local.major_version
minor_version = local.minor_version
summary = local.summary
files_created = [
"${local.resource_prefix}-config.json",
"deploy-${local.resource_prefix}.sh"
]
}
}
This example shows how to combine many functions to:
- Clean and format names consistently
- Extract version information
- Create environment-specific configurations
- Generate multiple types of resource names
- Build comprehensive tagging strategies
Function Chaining: Combining Multiple Functions
You can combine functions to do complex operations:
variable "user_emails" {
type = list(string)
default = ["John.Doe@COMPANY.COM", " jane.smith@company.com ", "BOB.JONES@COMPANY.COM"]
}
locals {
# Chain multiple functions together
clean_emails = [
for email in var.user_emails :
lower(trim(email, " "))
]
# Extract usernames (part before @)
usernames = [
for email in local.clean_emails :
split("@", email)[0]
]
# Create user configs
user_configs = {
for i, username in local.usernames :
username => {
email = local.clean_emails[i]
username = replace(username, ".", "_")
id = format("user_%03d", i + 1)
}
}
}
output "user_processing" {
value = {
original_emails = var.user_emails
clean_emails = local.clean_emails
usernames = local.usernames
user_configs = local.user_configs
}
}
Quick Reference
String Functions:
upper(string)
– make uppercaselower(string)
– make lowercasetitle(string)
– capitalize first letterstrim(string, chars)
– remove characters from start/endreplace(string, old, new)
– replace textformat(template, values...)
– format with templatejoin(separator, list)
– combine list with separator
List Functions:
length(list)
– count itemsconcat(list1, list2, ...)
– combine listscontains(list, value)
– check if item existssort(list)
– sort alphabeticallyreverse(list)
– flip orderdistinct(list)
– remove duplicatesslice(list, start, end)
– get part of list
Map Functions:
keys(map)
– get all keysvalues(map)
– get all valuesmerge(map1, map2, ...)
– combine maps
Math Functions:
min(numbers...)
– smallest numbermax(numbers...)
– largest numberceil(number)
– round upfloor(number)
– round downabs(number)
– absolute value
Type Functions:
tostring(value)
– convert to stringtonumber(value)
– convert to numbertoset(list)
– convert to set
Time Functions:
timestamp()
– current timeformatdate(format, time)
– format time
File Functions:
file(path)
– read filejsonencode(value)
– convert to JSONjsondecode(string)
– parse JSON
What’s Next?
Awesome work! You’ve just learned about Terraform’s built-in toolkit:
String Functions – Text manipulation:
- ✅ Formatting, cleaning, and transforming text
- ✅ Combining and replacing text
- ✅ Case conversion and trimming
List Functions – Working with collections:
- ✅ Counting, sorting, and organizing lists
- ✅ Combining and filtering data
- ✅ Finding and extracting items
Map Functions – Key-value operations:
- ✅ Extracting keys and values
- ✅ Merging and combining maps
- ✅ Building complex data structures
Utility Functions – Math, time, and conversions:
- ✅ Mathematical calculations
- ✅ Date and time formatting
- ✅ Type conversions and file operations
Advanced Patterns:
- ✅ Function chaining for complex operations
- ✅ Smart naming conventions
- ✅ Data transformation pipelines
In our next post, we’ll explore Terraform Modules – the way to package and reuse your Terraform code. You’ll learn:
- Creating your first module
- Using modules from the Terraform Registry
- Passing data between modules
- Organizing complex infrastructure with modules
- Best practices for module design
The function knowledge you gained today will be super useful for building flexible, reusable modules!
Ready to learn how to package your Terraform knowledge into reusable modules? The next post will show you how to build like a pro!