lesson ── terraform-intermediate ── ~14 мин ── 5 шагов
Часто Terraform надо отдать «текстовый файл с подстановками», bash-скрипт в user_data EC2, JSON-policy для IAM, YAML для cloud-init. Inline в HCL тяжело читать. Зашить в .tf готовый текст, нельзя, нужны подстановки.
templatefile() решает: читает файл шаблона, подставляет переменные,
возвращает строку. В этом уроке отрендеришь IAM-policy из шаблона.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Создай файл templates/bucket-rw-policy.json.tpl:
cd /home/student/tf-template
cat > templates/bucket-rw-policy.json.tpl <<'EOF'
{"Version": "2012-10-17",
"Statement": [
{"Sid": "ReadBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"${bucket_arn}", "${bucket_arn}/*"]
}%{ if write_enabled }, {"Sid": "WriteBucket",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "${bucket_arn}/*" }%{ endif }]
}
EOF
Синтаксис:
${var}, простая подстановка.%{ if cond }...%{ endif }, условный блок. Тут добавляется
второй Statement только если write_enabled = true.%{ for x in list }...%{ endfor }, цикл (тут не используем, но
бывает).Файл .tpl, просто условие. Можно .tftpl или без расширения.
Расширение .tpl или .tftpl, чтобы IDE/git-tools знали что это
шаблон, не финальный JSON.
✓ Шаблон записан. Теперь: рендер в HCL.
В /home/student/tf-template/main.tf:
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-template-${random_id.suffix.hex}"}
locals {bucket_policy_json = templatefile(
"${path.module}/templates/bucket-rw-policy.json.tpl", {bucket_arn = aws_s3_bucket.demo.arn
write_enabled = true
},
)
}
resource "aws_iam_policy" "bucket_rw" { name = "linuxlab-template-rw-${random_id.suffix.hex}"policy = local.bucket_policy_json
}
output "policy_json" {value = local.bucket_policy_json
}
Что важно:
path.module, абсолютный путь к директории текущего модуля
(тут root). Не пиши абсолютный путь руками, используй path.module.bucket_arn из ресурса, который ещё не создан. Terraform
автоматически выстроит зависимость: policy создастся после
бакета. См. tf-depends-on.aws_iam_policy.policy.✓ HCL готов. Теперь рендер можно увидеть через console.
Можно посмотреть результат рендера не делая apply:
cd /home/student/tf-template
terraform init
echo 'templatefile("${path.module}/templates/bucket-rw-policy.json.tpl", { bucket_arn = "arn:aws:s3:::test-bucket", write_enabled = true })' | terraform console -no-colorConsole выведет JSON с подставленным ARN. Можешь проиграть с
write_enabled = false, увидишь, что второй Statement исчезает.
Это главный способ дебага шаблонов. Не запускай apply чтобы понять, правильно ли отрендерилось, спроси console.
См. tf-console.
✓ Рендер виден до apply. Это сэкономит часы дебага.
terraform apply -auto-approve
Должны создаться: бакет, IAM policy, random_id.
Проверь policy через AWS CLI (LocalStack):
aws --endpoint-url=http://localstack:4566 iam list-policies | jq '.Policies[] | select(.PolicyName | startswith("linuxlab-template"))'И через terraform output:
terraform output -raw policy_json | jq .
Видишь итоговый JSON со всеми подстановками.
✓ IAM-policy создана из шаблона. templatefile в боевых условиях.
OpenTofu держит CLI и state совместимыми с Terraform по командам
этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе
сделай backup state и прогон на feature-branch - расхождения
концентрируются в новых фичах (variables в backend,
state-encryption, OCI registry-backed модули). См.
tf-opentofu-parity для полной матрицы.
Поменяй в HCL:
locals {bucket_policy_json = templatefile(
"${path.module}/templates/bucket-rw-policy.json.tpl", {bucket_arn = aws_s3_bucket.demo.arn
write_enabled = false # ← было true
},
)
}
terraform plan
Plan покажет diff: вторая Statement-секция (WriteBucket) исчезает
из policy JSON. Terraform увидит это как изменение атрибута policy
у aws_iam_policy.bucket_rw.
terraform apply -auto-approve
Это главное преимущество templatefile: одно изменение HCL → детерминированный re-render → понятный diff в plan'е.
✓ Conditional блок отработал. Policy перевыпустилась без write-разрешений.
Три способа собрать строку в HCL:
# 1) inline в HCL, heredoc
policy = <<-EOT
{ "Version": "2012-10-17", ... }EOT
Хорошо для маленьких и без переменных. Плохо для больших. IDE не умеет в подсветку JSON внутри heredoc.
# 2) file(): внешний файл, без подстановок
policy = file("${path.module}/policy.json")IDE подсвечивает .json. Но это статика, если нужны подстановки (ARN бакета, имя env), не сработает.
# 3) templatefile(): внешний файл с подстановками
policy = templatefile("${path.module}/policy.json.tpl", { ... })Лучшее из обоих миров. IDE подсвечивает (если расширение .tpl/.tftpl зарегистрировано как JSON-с-шаблоном). Подстановки работают.
Правило: templatefile для всего что больше 5-7 строк или содержит подстановки. heredoc/file, для тривиальных случаев.
templatefile(path, vars) читает файл с ${var} и %{ for } синтаксисом,
возвращает строку. Удобно для крупных текстовых артефактов с подстановками.
Не путать с template_file data source, он устаревший с TF 0.12.
команды
terraform consoleпроверить рендер шаблона до planterraform planвидишь итоговое значение в plan'е (если не sensitive)концепции