Day 15: Built-in Functions in Terraform Part-II

Today, we’ll explore Terraform functions — powerful helpers that make your infrastructure more flexible and smart.
Terraform functions allow you to manipulate data, perform calculations, handle text, work with networks, and much more — all inside your configuration files.
🎯 Today’s Goals
By the end of this lesson, you’ll be able to:
Master essential Terraform functions
Use encoding and cryptographic functions
Work with date/time and filesystem functions
Apply functions to real-world scenarios
🔐 Encoding Functions
Terraform’s encoding functions convert data between different formats so you can use it across APIs, files, and configurations.
These are extremely useful when you:
pass data to AWS user_data or templates
store structured info in strings
interact with APIs that expect Base64, JSON, or YAML
🧩 1. base64encode() and base64decode()
📘 Description
These convert text into Base64 format and back again.
base64encode(string)→ encodes plain textbase64decode(base64_string)→ decodes back to original text
Base64 encoding is commonly used in:
AWS EC2 user_data scripts
Secrets stored in S3 or external systems
API payloads
🧠 Example
locals {
original = "Hello, World"
encoded = base64encode(local.original)
decoded = base64decode(local.encoded)
}
🧾 Output
encoded = "SGVsbG8sIFdvcmxkCg=="
decoded = "Hello, World"
💡 Real-world use case
Encoding a startup script for an EC2 instance:
resource "aws_instance" "app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
user_data = base64encode(file("${path.module}/scripts/startup.sh"))
}
→ AWS expects Base64 input, so this function is perfect.
💾 2. jsonencode() and jsondecode()
📘 Description
Terraform natively uses HCL (HashiCorp Configuration Language),
but many cloud APIs and tools (like AWS Lambda, Datadog, Kubernetes) require JSON input.
jsonencode(map or list)→ converts Terraform data into JSON stringjsondecode(json_string)→ parses JSON back into Terraform data types
🧠 Example
locals {
data = {
name = "Terraform"
version = 1.6
}
json_string = jsonencode(local.data)
parsed = jsondecode(local.json_string)
}
🧾 Output
json_string = "{\"name\":\"Terraform\",\"version\":1.6}"
parsed = { name = "Terraform", version = 1.6 }
💡 Real-world use case
You can store configuration data in S3 or pass it to an API in JSON format:
resource "aws_s3_object" "config" {
bucket = "my-config-bucket"
key = "app_config.json"
content = jsonencode({
env = "production"
version = "1.0.0"
enabled = true
})
}
This automatically converts the Terraform map into a JSON file inside S3.
🧱 3. yamlencode() and yamldecode()
📘 Description
YAML is widely used for configuration files (like Kubernetes manifests or Ansible playbooks).
Terraform can convert its internal data structures to YAML and back.
yamlencode()→ Converts Terraform data → YAML stringyamldecode()→ Parses YAML string → Terraform data
🧠 Example
locals {
data = {
app = "web"
port = 8080
}
yaml_string = yamlencode(local.data)
yaml_parsed = yamldecode(local.yaml_string)
}
🧾 Output
yaml_string:
app: web
port: 8080
yaml_parsed: { app = "web", port = 8080 }
💡 Real-world use case
Generate a Kubernetes deployment file dynamically:
resource "local_file" "k8s_manifest" {
content = yamlencode({
apiVersion = "v1"
kind = "Pod"
metadata = { name = "nginx" }
spec = {
containers = [{
name = "nginx"
image = "nginx:latest"
}]
}
})
filename = "${path.module}/nginx.yaml"
}
📋 4. csvdecode()
📘 Description
Parses a CSV-formatted string into a list of maps.
This is helpful when reading structured data from CSV files like user lists, IPs, or configuration tables.
🧠 Example
locals {
csv_data = csvdecode("name,age\nAlice,30\nBob,25")
}
🧾 Output
[
{ name = "Alice", age = "30" },
{ name = "Bob", age = "25" }
]
💡 Real-world use case
Imagine you have a CSV file of instance names and sizes:
name,type
web,t2.micro
db,t3.micro
You can load and use it:
locals {
instances = csvdecode(file("${path.module}/instances.csv"))
}
resource "aws_instance" "csv_based" {
for_each = { for row in local.instances : row.name => row }
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value.type
tags = {
Name = each.value.name
}
}
✅ Automatically creates EC2 instances from your CSV!
🔑 Cryptographic Functions
These functions deal with data integrity, security, and uniqueness.
They are especially important when:
creating hashed passwords
verifying file content integrity
generating stable resource names
🔹 1. md5()
📘 Description
Generates a 128-bit MD5 hash of a string (commonly used for file integrity).
md5("Terraform") # => "e5bdf6e80e63f03d7a26dd9a88a234f0"
💡 Example use:
locals {
checksum = md5(file("${path.module}/app.zip"))
}
→ Useful for detecting when a file changes.
🔹 2. sha1(), sha256(), sha512()
📘 Description
These are stronger cryptographic hash functions —
they generate longer, more secure digests than MD5.
| Function | Hash Length | Typical Use |
| sha1() | 160-bit | Deprecated but still used for simple checks |
| sha256() | 256-bit | Most common for data integrity |
| sha512() | 512-bit | For maximum security |
🧠 Example
locals {
data = "Hello, Terraform!"
sha1_value = sha1(local.data)
sha256_value = sha256(local.data)
sha512_value = sha512(local.data)
}
🔹 3. bcrypt()
📘 Description
Generates a secure hash for passwords using the bcrypt algorithm.
Unlike SHA, bcrypt hashes are salted — even if the same password is hashed twice, the result is different.
🧠 Example
locals {
password = "supersecret"
bcrypt_hash = bcrypt(local.password)
}
Output → $2a$10$Jd2P8y... (random each time)
💡 Common in user provisioning:
resource "aws_iam_user_login_profile" "user" {
user = "admin"
pgp_key = "keybase:exampleuser"
password_reset_required = false
password = bcrypt("MySecurePassword123")
}
🧠 Summary
| Function | Purpose | Example Use |
| base64encode / decode | Convert text to/from Base64 | EC2 user_data |
| jsonencode / decode | Convert between Terraform maps and JSON | Store configs in S3 |
| urlencode | Prepare strings for API URLs | API queries |
| yamlencode / decode | Work with YAML config files | Kubernetes manifests |
| csvdecode | Parse CSV into list of maps | Bulk instance creation |
| md5 / sha1 / sha256 / sha512 | Create hashes for integrity/security | Verify file changes |
| bcrypt | Secure password hashing | IAM user creation |
📅 Date and Time Functions
These are used to manage timestamps — such as for naming resources with deployment time, scheduling, auditing, or versioning.
🕒 1. timestamp()
Purpose:
Returns the current UTC timestamp in ISO 8601 format (e.g., "2025-10-22T08:00:00Z").
locals {
now = timestamp()
}
Example Output:
"2025-10-22T08:00:00Z"
✅ Use Cases:
Tagging resources with deployment time.
Creating unique names for artifacts.
Logging when a deployment occurred.
tags = {
CreatedAt = timestamp()
}
🗓️ 2. formatdate(format, timestamp)
Purpose:
Formats a timestamp into a human-readable string using custom format tokens.
locals {
formatted1 = formatdate("DD MMM YYYY hh:mm:ss", timestamp())
formatted2 = formatdate("YYYY-MM-DD", timestamp())
}
Output:
"22 Oct 2025 14:30:00"
"2025-10-22"
✅ Use Cases:
Version tagging (e.g.,
v2025-10-22)Logging or backup filenames
Human-readable labels in outputs
output "build_version" {
value = "v${formatdate("YYYYMMDD-hhmm", timestamp())}"
}
⏩ 3. timeadd(timestamp, duration)
Purpose:
Adds a duration to a timestamp (supports seconds, minutes, hours, days, weeks).
locals {
now = timestamp()
tomorrow = timeadd(local.now, "24h")
next_week = timeadd(local.now, "168h") # 7 days
}
✅ Use Cases:
Setting expiry times (e.g., certificate expiry, temporary access)
Scheduling delayed actions
📁 Filesystem Functions
These allow Terraform to interact with files on your local filesystem — reading, checking, finding, or rendering templates.
📄 1. file(path)
Purpose:
Reads the contents of a file as a string.
locals {
script_content = file("${path.module}/scripts/init.sh")
}
✅ Use Cases:
Load shell scripts, config files, or policies to inject into resources.
Pass user_data to EC2 or container startup scripts.
✅ 2. fileexists(path)
Purpose:
Checks whether a file exists and returns true or false.
locals {
has_config = fileexists("${path.module}/config.yaml")
}
✅ Use Cases:
Conditional logic to include optional files.
Avoid Terraform errors when optional inputs are missing.
📂 3. fileset(directory, pattern)
Purpose:
Returns a list of filenames in a directory matching a pattern (e.g., all .sh files).
locals {
all_scripts = fileset(path.module, "scripts/*.sh")
}
✅ Use Cases:
Automatically include all scripts or templates in a directory.
Create multiple resources (e.g., upload all scripts to S3).
🧩 4. templatefile(path, vars)
Purpose:
Reads a file and replaces placeholders with variable values.
Placeholders use the ${var_name} syntax.
locals {
user_data = templatefile("${path.module}/templates/user-data.sh", {
hostname = "web-server"
port = 8080
})
}
If user-data.sh contains:
#!/bin/bash
echo "Starting ${hostname} on port ${port}"
✅ Output:
#!/bin/bash
echo "Starting web-server on port 8080"
✅ Use Cases:
- Dynamically generate scripts, configuration files, or manifest templates.
🧭 5. Path Utility Functions
| Function | Description | Example | Output |
dirname(path) | Returns the directory portion | dirname("/opt/data/file.txt") | /opt/data |
basename(path) | Returns filename | basename("/opt/data/file.txt") | file.txt |
abspath(path) | Converts relative path to absolute | abspath("./relative") | /home/user/project/relative |
pathexpand(path) | Expands ~ to home directory | pathexpand("~/data") | /home/username/data |
✅ Use Cases:
Managing file paths in reusable modules.
Normalizing paths across systems.
🌐 IP Network Functions
These functions help manipulate and calculate IP network addresses — crucial for VPC, subnets, and CIDR management in AWS, Azure, or GCP.
🌍 1. cidrhost(cidr, hostnum)
Purpose:
Returns a specific host IP address from a given subnet.
locals {
vpc_cidr = "10.0.0.0/16"
first_ip = cidrhost(local.vpc_cidr, 0)
second_ip = cidrhost(local.vpc_cidr, 1)
}
Output:
"10.0.0.0"
"10.0.0.1"
✅ Use Cases:
Assign static IPs in subnets.
Generate predictable host IPs.
🧱 2. cidrnetmask(cidr)
Purpose:
Returns the netmask of a CIDR block.
cidrnetmask("10.0.0.0/16") # => "255.255.0.0"
✅ Use Case: Documentation or tagging (helps identify subnet size easily).
🧩 3. cidrsubnet(cidr, newbits, netnum)
Purpose:
Subdivides a larger CIDR into smaller subnet blocks.
locals {
subnet1 = cidrsubnet("10.0.0.0/16", 8, 0) # 10.0.0.0/24
subnet2 = cidrsubnet("10.0.0.0/16", 8, 1) # 10.0.1.0/24
}
✅ Use Cases:
- Create multiple subnets dynamically for different environments or AZs.
🧮 4. cidrsubnets(cidr, newbits...)
Purpose:
Generates multiple subnets at once from one parent network.
locals {
subnets = cidrsubnets("10.0.0.0/16", 8, 8, 8)
}
Output:
["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
✅ Use Cases:
- Automatically allocate subnets for each application layer.
🧪 Hands-On Lab
📁project structure
terraform-day15/
├── main.tf
├── variables.tf
├── locals.tf
├── outputs.tf
├── scripts/
│ └── init.sh
├── templates/
│ └── user-data.sh
└── config.yaml
This lab uses Date/Time, Filesystem, IP Network, and Type Conversion functions all together.
🧠 Scenario
We’ll simulate deploying a web server into a generated subnet inside a VPC.
Terraform will:
Calculate subnets using IP functions. 🧮
Generate timestamps and version tags using Date/Time functions ⏱️
Read files and templates using Filesystem functions 📁
Use Type Conversion functions for safe, flexible data handling 🔄
1️⃣ variables.tf
variable "project_name" {
description = "Project name for tagging and resource naming"
type = string
default = "terraform-lab"
}
variable "region" {
description = "AWS region to deploy to"
type = string
default = "us-east-1"
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "enable_public_access" {
description = "Allow public access to the instance?"
type = string
default = "true" # as string for demonstration of tobool()
}
2️⃣ locals.tf
locals {
# -------------------------
# 📅 Date & Time Functions
# -------------------------
current_time = timestamp()
formatted_time = formatdate("YYYY-MM-DD hh:mm:ss", local.current_time)
build_version = formatdate("YYYYMMDD-hhmm", local.current_time)
expire_time = timeadd(local.current_time, "168h") # +7 days
# -------------------------
# 🌐 IP Network Functions
# -------------------------
vpc_cidr = var.vpc_cidr
subnet1 = cidrsubnet(local.vpc_cidr, 8, 0) # "10.0.0.0/24"
subnet2 = cidrsubnet(local.vpc_cidr, 8, 1) # "10.0.1.0/24"
first_ip = cidrhost(local.subnet1, 10)
netmask = cidrnetmask(local.vpc_cidr)
# -------------------------
# 📁 Filesystem Functions
# -------------------------
script_content = file("${path.module}/scripts/init.sh")
has_config = fileexists("${path.module}/config.yaml")
all_scripts = fileset(path.module, "scripts/*.sh")
# Render a user-data template with variables
user_data = templatefile("${path.module}/templates/user-data.sh", {
hostname = "${var.project_name}-server"
port = 8080
})
# -------------------------
# 🔄 Type Conversion Functions
# -------------------------
is_public = tobool(var.enable_public_access)
number_test = try(tonumber("123"), 0)
is_number = can(tonumber("abc")) # false
# Convert set → list → string for display
tags_set = toset(["app", "terraform", "infra", "app"])
tags_list = tolist(local.tags_set)
tags_str = tostring(join(",", local.tags_list))
}
3️⃣ main.tf
(This simulates deployment logic — no real AWS resources needed, you can run this locally and observe outputs.)
terraform {
required_version = ">= 1.6.0"
}
provider "aws" {
region = var.region
}
# Example local resource to simulate usage of all functions
resource "null_resource" "example" {
triggers = {
build_version = local.build_version
subnet = local.subnet1
netmask = local.netmask
has_config = tostring(local.has_config)
tags = local.tags_str
}
provisioner "local-exec" {
command = "echo '🚀 Deploying ${var.project_name} version ${local.build_version}'"
}
}
# Save rendered user-data to file for demonstration
resource "local_file" "rendered_user_data" {
content = local.user_data
filename = "${path.module}/rendered-user-data.sh"
}
4️⃣ outputs.tf
output "📅_date_time" {
value = {
now = local.current_time
formatted = local.formatted_time
version_tag = local.build_version
expires_at = local.expire_time
}
}
output "🌐_network_details" {
value = {
vpc_cidr = local.vpc_cidr
subnet1 = local.subnet1
subnet2 = local.subnet2
first_ip = local.first_ip
netmask = local.netmask
}
}
output "📁_filesystem_info" {
value = {
config_exists = local.has_config
scripts_found = local.all_scripts
user_data = local.user_data
}
}
output "🔄_type_conversions" {
value = {
is_public = local.is_public
number_test = local.number_test
is_number = local.is_number
tags = local.tags_str
}
}
5️⃣ scripts/init.sh
#!/bin/bash
echo "Initializing system..."
echo "Date: $(date)"
6️⃣ templates/user-data.sh
#!/bin/bash
echo "🚀 Launching ${hostname}"
echo "Listening on port ${port}"
7️⃣ config.yaml
project: terraform-lab
environment: dev
owner: example@example.com
Run
# Initialize
terraform init
# Use console to test functions
terraform console
# Try these:
> cidrsubnet("10.0.0.0/16", 8, 0)
"10.0.0.0/24"
> cidrhost("10.0.1.0/24", 5)
"10.0.1.5"
> format("%s-%03d", "web", 5)
"web-005"
> timestamp()
"2025-10-22T09:38:22Z"
> formatdate("DD-MM-YYYY", timestamp())
"22-10-2025"
> md5("terraform")
"1b1ed905d54c18e3dd8828986c14be17"
> range(5)
tolist([
0,
1,
2,
3,
4,
])
> exit
# Apply
terraform apply -auto-approve
# View outputs
terraform output
#Output look like this
null_resource.example: Creating...
local_file.rendered_user_data: Creating...
null_resource.example: Provisioning with 'local-exec'...
local_file.rendered_user_data: Creation complete after 0s [id=2b67ae43a0bde8eaf87b28a86c7c9b9bf94cd3d5]
null_resource.example (local-exec): Executing: ["/bin/sh" "-c" "echo '🚀 Deploying terraform-lab version 20251022-0940'"]
null_resource.example (local-exec): 🚀 Deploying terraform-lab version 20251022-0940
null_resource.example: Creation complete after 0s [id=6127148458059718850]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
date_time = {
"expires_at" = "2025-10-29T09:40:25Z"
"formatted" = "2025-10-22 09:40:25"
"now" = "2025-10-22T09:40:25Z"
"version_tag" = "20251022-0940"
}
filesystem_info = {
"config_exists" = true
"scripts_found" = toset([
"scripts/init.sh",
])
"user_data" = <<-EOT
#!/bin/bash
echo "🚀 Launching terraform-lab-server"
echo "Listening on port 8080"
EOT
}
network_details = {
"first_ip" = "10.0.0.10"
"netmask" = "255.255.0.0"
"subnet1" = "10.0.0.0/24"
"subnet2" = "10.0.1.0/24"
"vpc_cidr" = "10.0.0.0/16"
}
type_conversions = {
"is_number" = false
"is_public" = true
"number_test" = 123
"tags" = "app,infra,terraform"
}
# Clean up
terraform destroy -auto-approve
📝 Summary
Today you learned:
✅ Encoding functions (base64, json, yaml)
✅ Crypto functions (md5, sha256, bcrypt)
✅ Date/time functions (timestamp, formatdate)
✅ Network functions (cidrsubnet, cidrhost)
✅ Filesystem functions (file, templatefile)
🚀 Tomorrow’s Preview
Day 16: Dynamic Blocks for Flexible Resources
Tomorrow we’ll:
Master dynamic blocks
Create flexible security groups
Build dynamic ingress/egress rules
Use for_each in dynamic blocks
Handle complex nested blocks
← Day 14: Conditionals & Logic | Day 16: Dynamic Blocks →
Remember: Functions are powerful tools for data transformation and manipulation!



