Terraform Expressions and Functions
Terraform’s expression language (HCL) includes dozens of built-in functions for transforming values, computing network addresses, encoding data, manipulating strings, and working with collections. Expressions are evaluated during terraform plan — no runtime execution.
String Functions
locals { # format — printf-style formatting bucket_name = format("mycompany-%s-%s", var.environment, var.region) # "mycompany-production-us-east-1"
# join / split az_string = join(", ", ["us-east-1a", "us-east-1b", "us-east-1c"]) # "us-east-1a, us-east-1b, us-east-1c"
parts = split(",", "api,auth,worker") # ["api", "auth", "worker"]
# upper / lower / title env_upper = upper(var.environment) # "PRODUCTION" env_lower = lower(var.environment) # "production"
# replace clean_name = replace(var.service_name, "_", "-") # "my_service" → "my-service"
# trimspace / trim / trimprefix / trimsuffix clean = trimspace(" hello world ") # "hello world"
# startswith / endswith is_api = startswith(var.service_name, "api-")
# substr short_name = substr(var.service_name, 0, 20) # First 20 chars
# length (for strings) name_length = length(var.service_name)
# contains (substring check) has_prod = strcontains(var.environment, "prod")}String Templates
# Interpolationresource "aws_cloudwatch_log_group" "app" { name = "/ecs/${var.environment}/${var.service_name}"}
# Heredoc for multi-linelocals { user_data = <<-EOT #!/bin/bash echo "Environment: ${var.environment}" echo "Service: ${var.service_name}" systemctl start myapp EOT}
# templatefile — render a template file with variablesresource "aws_instance" "web" { user_data = templatefile("${path.module}/templates/user-data.sh.tpl", { environment = var.environment app_version = var.app_version db_endpoint = aws_db_instance.main.endpoint })}Numeric Functions
locals { max_instances = max(var.min_capacity, 1) min_replicas = min(var.desired_count, var.max_count) abs_offset = abs(-5) # 5
# floor / ceil instances = ceil(var.vcpus / 4.0) # Round up to whole instances
# pow storage = pow(2, 10) # 1024}Collection Functions
locals { subnet_ids = ["subnet-01", "subnet-02", "subnet-03"] regions = ["us-east-1", "eu-west-1", "us-east-1"] # duplicate
# length count = length(local.subnet_ids) # 3
# distinct — remove duplicates (preserves order) unique_regions = distinct(local.regions) # ["us-east-1", "eu-west-1"]
# flatten — collapse nested lists all_ids = flatten([ ["subnet-01", "subnet-02"], ["subnet-03", "subnet-04"] ]) # ["subnet-01", "subnet-02", "subnet-03", "subnet-04"]
# concat — join lists all_subnets = concat(var.public_subnet_ids, var.private_subnet_ids)
# element — get element at index (wraps around) az = element(var.availability_zones, count.index % length(var.availability_zones))
# slice — sublist first_two_azs = slice(var.availability_zones, 0, 2)
# contains — membership check is_allowed = contains(["t3.micro", "t3.small"], var.instance_type)
# keys / values (for maps) env_names = keys(var.environments) env_values = values(var.environments)
# lookup — safe map access with default instance_type = lookup(var.instance_types_by_env, var.environment, "t3.micro")
# zipmap — build a map from lists of keys and values tag_map = zipmap(["Environment", "Team"], [var.environment, var.team])
# merge — combine maps (right-hand takes precedence) all_tags = merge( { ManagedBy = "terraform", Environment = var.environment }, var.additional_tags )
# toset / tolist / tomap — type conversions subnet_set = toset(var.subnet_ids) # For for_each}For Expressions
locals { # Transform a list upper_envs = [for e in var.environments : upper(e)]
# Filter a list prod_buckets = [for b in var.buckets : b if b.environment == "production"]
# Transform a list to a map bucket_by_name = { for b in var.buckets : b.name => b.arn }
# Transform a map tags_upper = { for k, v in var.tags : upper(k) => v }
# Filter a map enabled_services = { for k, v in var.services : k => v if v.enabled }
# Flatten nested structures all_users = flatten([ for team in var.teams : [ for user in team.members : { name = user team = team.name } ] ])}Network / CIDR Functions
locals { vpc_cidr = "10.0.0.0/16"
# cidrsubnet — compute subnets # cidrsubnet(prefix, newbits, netnum) subnet_0 = cidrsubnet(local.vpc_cidr, 8, 0) # "10.0.0.0/24" subnet_1 = cidrsubnet(local.vpc_cidr, 8, 1) # "10.0.1.0/24" subnet_2 = cidrsubnet(local.vpc_cidr, 8, 2) # "10.0.2.0/24"
# cidrhost — specific IP from CIDR gateway_ip = cidrhost(local.vpc_cidr, 1) # "10.0.0.1" broadcast = cidrhost(local.vpc_cidr, -1) # "10.0.255.255"
# cidrnetmask — get subnet mask netmask = cidrnetmask("10.0.0.0/16") # "255.255.0.0"
# cidrsubnets — compute multiple subnets at once all_subnets = cidrsubnets(local.vpc_cidr, 8, 8, 8, 8) # ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]}
# Auto-compute subnets for each AZresource "aws_subnet" "private" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index) availability_zone = var.availability_zones[count.index]}Encoding Functions
locals { # base64 encode/decode encoded = base64encode("Hello, World!") # "SGVsbG8sIFdvcmxkIQ==" decoded = base64decode("SGVsbG8sIFdvcmxkIQ==")
# JSON encode/decode config_json = jsonencode({ environment = var.environment services = var.service_names tags = var.tags })
config_map = jsondecode(data.aws_secretsmanager_secret_version.config.secret_string)
# MD5 / SHA (for change detection) script_hash = filemd5("${path.module}/scripts/bootstrap.sh") config_hash = sha256(jsonencode(var.app_config))}Filesystem Functions
locals { # file — read a file's contents ca_cert = file("${path.module}/certs/ca.pem")
# filebase64 — read and base64 encode cert_b64 = filebase64("${path.module}/certs/server.pem")
# filemd5 / filesha256 — hash a file script_hash = filemd5("${path.module}/scripts/init.sh")
# path.module — directory of the current module # path.root — root module directory # path.cwd — current working directory}Conditional Expression
locals { # condition ? true_value : false_value instance_type = var.environment == "production" ? "t3.large" : "t3.micro" replica_count = var.high_availability ? 3 : 1 log_retention = var.environment == "production" ? 90 : 7
# Nested conditionals (keep shallow for readability) size = ( var.environment == "production" ? "xlarge" : var.environment == "staging" ? "medium" : "small" )}