Skip to main content

Template Files

templatefile() function = render template file ด้วย variables — สำหรับ user-data, config files, IAM policies

templatefile() Function

templatefile(path, vars)
  • path — path ไฟล์ template
  • vars — map ของ variables ที่ส่งให้ template

ตัวอย่างพื้นฐาน

main.tf
resource "aws_instance" "web" {
ami = "ami-12345"
instance_type = "t2.micro"

user_data = templatefile("user_data.sh.tpl", {
server_name = "web-${var.env}"
port = 8080
})
}
user_data.sh.tpl
#!/bin/bash
echo "Starting ${server_name} on port ${port}"
yum install -y httpd
echo "<h1>${server_name}</h1>" > /var/www/html/index.html
systemctl start httpd

หลัง render:

#!/bin/bash
echo "Starting web-prod on port 8080"
yum install -y httpd
echo "<h1>web-prod</h1>" > /var/www/html/index.html
systemctl start httpd

Template Syntax

ใช้ syntax เดียวกับ HCL:

Interpolation

echo "${variable_name}"

Directive

%{ if condition }
...
%{ else }
...
%{ endif }
%{ for item in list }
${item}
%{ endfor }

ตัวอย่าง: Conditional Logic

main.tf
resource "aws_instance" "web" {
user_data = templatefile("init.sh.tpl", {
install_monitoring = var.enable_monitoring
monitoring_url = var.monitoring_url
})
}
init.sh.tpl
#!/bin/bash
yum install -y httpd

%{ if install_monitoring ~}
# Install monitoring agent
curl -L ${monitoring_url} | bash
%{ endif ~}

systemctl start httpd

ตัวอย่าง: Loop

resource "aws_instance" "web" {
user_data = templatefile("config.tpl", {
services = ["nginx", "redis", "memcached"]
})
}
config.tpl
#!/bin/bash
%{ for svc in services ~}
yum install -y ${svc}
systemctl enable ${svc}
systemctl start ${svc}
%{ endfor ~}

หลัง render:

#!/bin/bash
yum install -y nginx
systemctl enable nginx
systemctl start nginx
yum install -y redis
systemctl enable redis
systemctl start redis
yum install -y memcached
systemctl enable memcached
systemctl start memcached
Whitespace Stripping
  • ~} → strip trailing whitespace
  • ${~ var} → strip leading whitespace

ตัวอย่าง: nginx Config

resource "aws_instance" "web" {
user_data = templatefile("nginx.conf.tpl", {
domain = "example.com"
backend_servers = aws_instance.app[*].private_ip
})
}
nginx.conf.tpl
upstream backend {
%{ for ip in backend_servers ~}
server ${ip}:8080;
%{ endfor ~}
}

server {
listen 80;
server_name ${domain};

location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}

ตัวอย่าง: IAM Policy

resource "aws_iam_role_policy" "lambda" {
role = aws_iam_role.lambda.id

policy = templatefile("policies/lambda.json.tpl", {
s3_bucket_arn = aws_s3_bucket.data.arn
sns_topic_arn = aws_sns_topic.alerts.arn
})
}
policies/lambda.json.tpl
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "${s3_bucket_arn}/*"
},
{
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "${sns_topic_arn}"
}
]
}

ตัวอย่าง: Multi-Line YAML

resource "kubernetes_manifest" "deployment" {
manifest = yamldecode(templatefile("deployment.yaml.tpl", {
name = "myapp"
image = "nginx:1.25"
replicas = 3
namespace = "default"
}))
}
deployment.yaml.tpl
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${name}
namespace: ${namespace}
spec:
replicas: ${replicas}
selector:
matchLabels:
app: ${name}
template:
metadata:
labels:
app: ${name}
spec:
containers:
- name: ${name}
image: ${image}
ports:
- containerPort: 80

วิธีใช้ที่ดี

Pattern 1: Pass Object

resource "aws_instance" "web" {
user_data = templatefile("init.sh.tpl", {
config = {
env = "prod"
log_level = "info"
features = ["auth", "logging"]
}
})
}
init.sh.tpl
export ENV=${config.env}
export LOG_LEVEL=${config.log_level}

%{ for f in config.features ~}
echo "Enable feature: ${f}"
%{ endfor ~}

Pattern 2: Use jsonencode for JSON

resource "aws_instance" "web" {
user_data = templatefile("init.sh.tpl", {
db_config_json = jsonencode({
host = aws_db_instance.main.address
port = aws_db_instance.main.port
database = aws_db_instance.main.db_name
})
})
}
init.sh.tpl
echo '${db_config_json}' > /etc/app/db.json

Pattern 3: Heredoc Inline (no file)

ถ้าไม่อยากแยกไฟล์:

locals {
user_data = <<-EOT
#!/bin/bash
echo "Server: ${var.server_name}"
yum install -y ${var.package}
EOT
}

resource "aws_instance" "web" {
user_data = local.user_data
}

→ Heredoc ใช้ interpolation เหมือนกัน — แต่ไม่มี directive (%{ if })

ทางเลือก: file() function

ถ้าไม่ต้องการ interpolation:

resource "aws_instance" "web" {
user_data = file("user_data.sh") # อ่าน raw, ไม่ render
}

Common Pitfalls

1. Quote Hell

# ❌
templatefile("script.tpl", {
message = "He said \"hello\""
})
# ✅ ใช้ heredoc
templatefile("script.tpl", {
message = <<-EOT
He said "hello"
EOT
})

2. Multi-line String

# ❌ Newline ใน string ตรงๆ
templatefile("x.tpl", {
config = "line1
line2"
})

# ✅ Heredoc
templatefile("x.tpl", {
config = <<-EOT
line1
line2
EOT
})

3. Loop ที่ Empty

%{ for x in [] ~}
${x}
%{ endfor ~}

→ ไม่ render อะไร — OK ไม่ error

4. Undefined Variable

templatefile("x.tpl", {
foo = "bar"
})
x.tpl
${foo}
${baz} # ← Error: undeclared variable

ต้อง pass ทุก variable ที่ template ใช้

ตัวอย่าง: Real-World Bootstrap

main.tf
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type

user_data = base64encode(templatefile("user_data.sh.tpl", {
cluster_name = var.cluster_name
monitoring_url = var.monitoring_endpoint
log_group = aws_cloudwatch_log_group.app.name
secret_arn = aws_secretsmanager_secret.app.arn
install_features = ["auth", "metrics", "tracing"]
}))

iam_instance_profile = aws_iam_instance_profile.web.name
}
user_data.sh.tpl
#!/bin/bash
set -e

# Install CloudWatch agent
yum install -y amazon-cloudwatch-agent
cat > /opt/aws/amazon-cloudwatch-agent/etc/config.json <<EOF
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [{
"file_path": "/var/log/app.log",
"log_group_name": "${log_group}"
}]
}
}
}
}
EOF
systemctl start amazon-cloudwatch-agent

# Fetch secret
SECRET=$(aws secretsmanager get-secret-value --secret-id "${secret_arn}" --query SecretString --output text)
echo "$SECRET" > /etc/app/secret.json

# Install features
%{ for feature in install_features ~}
echo "Installing feature: ${feature}"
yum install -y app-${feature}
systemctl enable app-${feature}
%{ endfor ~}

# Connect to cluster
echo "${cluster_name}" > /etc/app/cluster
systemctl start app

# Heartbeat to monitoring
curl -X POST ${monitoring_url}/heartbeat -d "host=$(hostname)"

สรุป

  • templatefile(path, vars) = render template
  • ใช้ interpolation ${var} + directive %{ if }, %{ for }
  • Whitespace stripping: ~}, ${~ ...}
  • ใช้สำหรับ user-data, IAM policy, K8s manifest, config file
  • ทางเลือก: file() (no render), heredoc (inline)
  • Pass complex types ด้วย jsonencode() / yamlencode()

ต่อไป → Section 15: CI/CD & Workspaces