Terraform Functions: Where Your Infrastructure Gets Superpowers

Published: (December 7, 2025 at 06:31 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Introduction

During the AWS 30‑Day Challenge I spent two full days exploring Terraform’s built‑in functions. They turned many long, error‑prone configurations into concise, production‑ready code. Below are the most useful patterns I discovered, each illustrated with a practical example.

Using the Terraform Console

Before editing main.tf, spin up the interactive console to test expressions:

terraform console
> lower("HELLO WORLD")
"hello world"

> max(5, 12, 9)
12

> split(",", "80,443,8080")
[
  "80",
  "443",
  "8080",
]

> reverse(["a", "b", "c"])
[
  "c",
  "b",
  "a",
]

The console lets you verify logic without any deployment.

1. Normalizing Resource Names

Problem – AWS resources often require lowercase, hyphenated names.

Solution

locals {
  clean_name = lower(replace(var.project_name, " ", "-"))
}

resource "aws_resourcegroups_group" "project" {
  name = local.clean_name
}

lower() and replace() are essential for consistent naming.

2. Sanitizing S3 Bucket Names

Problem – S3 bucket names must be lowercase, contain no underscores or special characters, and be ≤ 63 characters.

Solution

locals {
  sanitized_bucket_name = substr(
    lower(
      replace(
        replace(var.bucket_name_input, "_", "-"),
        "/[^a-z0-9-]/", ""
      )
    ),
    0,
    63
  )
}

Nested functions let you replace underscores, strip invalid characters, lowercase, and truncate—all in one expression.

3. Generating Security‑Group Rules from a CSV

Problem – Turning a comma‑separated list of ports into individual ingress blocks.

Solution

variable "allowed_ports" {
  default = "80,443,8080"
}

locals {
  ports_list = split(",", var.allowed_ports)
}

resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = local.ports_list
    content {
      from_port   = tonumber(ingress.value)
      to_port     = tonumber(ingress.value)
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

split() converts the string to a list, which drives a dynamic block.

4. Selecting Instance Types per Environment

Problem – Different environments need different instance sizes.

Solution

variable "environment" {
  default = "dev"
}

locals {
  instance_types = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }

  selected_type = lookup(local.instance_types, var.environment, "t3.micro")
}

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = local.selected_type
}

lookup() provides map‑based conditional logic with a safe fallback.

5. Validating Instance‑Type Patterns

Problem – Enforce that instance types follow a company naming convention (e.g., t2.* or t3.*).

Solution

variable "instance_type" {
  type = string

  validation {
    condition     = can(regex("^(t2|t3)\\.", var.instance_type))
    error_message = "Instance type must start with 't2.' or 't3.'"
  }
}

regex() checks the pattern, while can() safely returns false instead of erroring.

6. Naming and Secret Handling

Problem – Ensure backup names follow a convention and keep passwords secret.

Solution

variable "backup_name" {
  type = string

  validation {
    condition     = endswith(var.backup_name, "-backup")
    error_message = "Backup name must end with '-backup'"
  }
}

variable "db_password" {
  type      = string
  sensitive = true
}

output "backup_info" {
  value = {
    name     = var.backup_name
    password = sensitive(var.db_password)
  }
  sensitive = true
}

endswith()/startswith() validate strings, and sensitive() prevents secret leakage.

7. Cost Calculations

Problem – Compute total monthly cost, apply credits, and find the highest single expense.

Solution

variable "monthly_costs" {
  default = [120.50, 85.00, 200.75, 50.00]
}

variable "credit" {
  default = -50.00  # Negative credit
}

locals {
  total_cost    = sum(var.monthly_costs)
  actual_credit = abs(var.credit)
  final_cost    = max(local.total_cost - local.actual_credit, 0)
  highest_cost  = max(var.monthly_costs...)
}

output "cost_summary" {
  value = {
    total_before_credit = local.total_cost
    credit_applied      = local.actual_credit
    final_bill          = local.final_cost
    highest_single_cost = local.highest_cost
  }
}

sum(), abs(), max(), and the splat operator (...) make arithmetic straightforward.

8. Timestamp Formatting

Problem – Generate consistent timestamps for tags and resource names.

Solution

locals {
  current_time = timestamp()

  formatted_tags = {
    CreatedAt  = formatdate("YYYY-MM-DD hh:mm:ss ZZZ", local.current_time)
    BackupDate = formatdate("YYYY-MM-DD", local.current_time)
    Year       = formatdate("YYYY", local.current_time)
  }
}

resource "aws_s3_bucket" "backups" {
  bucket = "backups-${formatdate("YYYY-MM-DD", timestamp())}"
  tags   = local.formatted_tags
}

timestamp() returns UTC time, and formatdate() lets you shape it as needed.

9. Reading JSON Configs and Storing Secrets

Problem – Load a JSON file with database credentials and store them in AWS Secrets Manager.

Solution

locals {
  # Verify the file exists
  config_exists = fileexists("${path.module}/config/database.json")

  # Read and decode JSON (only if the file is present)
  db_config = jsondecode(
    file("${path.module}/config/database.json")
  )
}

resource "aws_secretsmanager_secret" "db" {
  name = "db-credentials"
}

resource "aws_secretsmanager_secret_version" "db_version" {
  secret_id     = aws_secretsmanager_secret.db.id
  secret_string = jsonencode(local.db_config)
}
Back to Blog

Related posts

Read more »