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 end
  • upper() makes everything UPPERCASE
  • lower() makes everything lowercase
  • title() 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 values
  • join() 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 list
  • concat() combines multiple lists into one
  • contains() 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 order
  • reverse() flips the order
  • distinct() removes duplicate items
  • slice(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 number
  • floor() rounds down to the previous whole number
  • min() finds the smallest number
  • max() finds the largest number
  • abs() 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 number
  • tostring() converts number to text
  • toset() 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 time
  • formatdate(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 file
  • jsondecode() converts JSON text to Terraform data
  • jsonencode() converts Terraform data to JSON text
  • base64encode() and base64decode() 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 uppercase
  • lower(string) – make lowercase
  • title(string) – capitalize first letters
  • trim(string, chars) – remove characters from start/end
  • replace(string, old, new) – replace text
  • format(template, values...) – format with template
  • join(separator, list) – combine list with separator

List Functions:

  • length(list) – count items
  • concat(list1, list2, ...) – combine lists
  • contains(list, value) – check if item exists
  • sort(list) – sort alphabetically
  • reverse(list) – flip order
  • distinct(list) – remove duplicates
  • slice(list, start, end) – get part of list

Map Functions:

  • keys(map) – get all keys
  • values(map) – get all values
  • merge(map1, map2, ...) – combine maps

Math Functions:

  • min(numbers...) – smallest number
  • max(numbers...) – largest number
  • ceil(number) – round up
  • floor(number) – round down
  • abs(number) – absolute value

Type Functions:

  • tostring(value) – convert to string
  • tonumber(value) – convert to number
  • toset(list) – convert to set

Time Functions:

  • timestamp() – current time
  • formatdate(format, time) – format time

File Functions:

  • file(path) – read file
  • jsonencode(value) – convert to JSON
  • jsondecode(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!

Akhilesh Mishra

Akhilesh Mishra

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

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

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