lesson ── terraform-intermediate ── ~14 мин ── 5 шагов
Terraform plan показывает что будет сделано, но не проверяет «а имеет
ли это смысл». Например: бакет создаётся в us-west-2, а у тебя в HCL
написано «должен быть в us-east-1», отдельно не ловится.
С TF 1.2+ есть precondition и postcondition внутри lifecycle, это
declarative assertions. Если не выполняются, apply останавливается.
С TF 1.5+ есть check блоки, мягкие проверки без блокировки apply.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-checks
cat > main.tf <<'EOF'
variable "bucket_name_prefix" {type = string
default = "linuxlab-checks"
}
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "${var.bucket_name_prefix}-${random_id.suffix.hex}"}
EOF
terraform init
terraform apply -auto-approve
✓ База создана. Добавим инварианты.
Добавь lifecycle с preconditions в aws_s3_bucket.demo:
resource "aws_s3_bucket" "demo" { bucket = "${var.bucket_name_prefix}-${random_id.suffix.hex}" lifecycle { precondition {condition = length(var.bucket_name_prefix) >= 3 && length(var.bucket_name_prefix) <= 40
error_message = "bucket_name_prefix must be 3-40 chars (to fit total bucket name into S3 63-char limit)."
}
}
}
Проверь что это работает, поменяй default на короткое:
sed -i 's/default = "linuxlab-checks"/default = "x"/' main.tf
terraform plan
Должна быть ошибка:
Error: Resource precondition failed
on main.tf line ..., in resource "aws_s3_bucket" "demo":
...: condition = ...
bucket_name_prefix must be 3-40 chars ...
Plan останавливается до похода в облако. Это валидация на стадии plan. См. tf-resource-lifecycle и tf-variable.
Верни значение обратно:
sed -i 's/default = "x"/default = "linuxlab-checks"/' main.tf
✓ precondition сработал. Plan теперь защищён от плохих входов.
Postcondition, после получения атрибутов от провайдера. Проверим что бакет создан в правильном регионе:
resource "aws_s3_bucket" "demo" { bucket = "${var.bucket_name_prefix}-${random_id.suffix.hex}" lifecycle { precondition {condition = length(var.bucket_name_prefix) >= 3 && length(var.bucket_name_prefix) <= 40
error_message = "bucket_name_prefix must be 3-40 chars."
}
postcondition {condition = self.region == "us-east-1"
error_message = "Expected region us-east-1, got ${self.region}."}
}
}
self внутри postcondition, это сам ресурс, доступны все его
атрибуты. Это главное отличие от precondition (там нет self,
ресурса ещё нет).
Применим:
terraform apply -auto-approve
Plan и apply проходят (LocalStack возвращает us-east-1, как и сконфигурировано).
✓ postcondition сработал. Атрибут ресурса проверен.
check. TF 1.5+, отдельный top-level блок. Не блокирует apply при
ошибке, только выводит warning:
Добавь в конец main.tf:
check "bucket_versioning" { assert {condition = aws_s3_bucket.demo.versioning != null && length(aws_s3_bucket.demo.versioning) > 0
error_message = "Bucket has no versioning configured: warning, not blocker."
}
}
terraform apply -auto-approve
Должно вывести warning (для нашего бакета versioning не сконфигурирован):
Warning: Check block assertion failed
on main.tf line ...:
...
Bucket has no versioning configured...
Apply прошёл, только warning. Это разница с precondition (там апплай бы упал): check, для мониторинга drift и «нежелательных» состояний, которые не должны блокировать deployment. Например: «бакет должен быть versioning'ed, но если ещё не, не ломаем pipeline, шлём warning в alerting».
✓ check блок отработал warning'ом. Не блокирует apply.
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 для полной матрицы.
Все три механизма похожи. Различия:
| Механизм | Когда работает | Поведение при failure |
|---|---|---|
variable.validation | На variable | Ошибка plan, до всего |
lifecycle.precondition | Перед созданием/изменением ресурса | Ошибка plan |
lifecycle.postcondition | После refresh, до записи state | Ошибка apply |
check { assert } | Параллельно apply | Warning, apply продолжается |
Правила:
variable.validation.Не злоупотребляй. Каждая проверка = вес для plan'а. Если можно
сделать невалидное состояние невозможным через типы, лучше так.
Например type = number уже исключает «бакет создавать -1 раз» без
validation.
✓ Различия ясны. Используй уместное.
Самый практичный кейс preconditions, связь ресурсов:
resource "aws_iam_role" "lambda" {name = "demo-lambda"
# ...
}
resource "aws_lambda_function" "demo" {function_name = "demo"
role = aws_iam_role.lambda.arn
lifecycle { precondition { condition = startswith(aws_iam_role.lambda.assume_role_policy, "{")error_message = "IAM role must have a valid JSON assume_role_policy."
}
}
}
Это «защитный пояс», даже если IAM-роль создалась криво, лямбда не пойдёт за ней и не получит непонятную AWS-ошибку.
Антипаттерн: писать precondition про то, что Terraform уже
проверит на этапе типа. Если ты делаешь precondition с
condition = var.foo != null, но variable "foo" { type = string }
без default. Terraform и так не позволит передать null. Дубль.
precondition запускается до создания ресурса; postcondition, после
того как state знает атрибуты; check { assert { } }, отдельный
блок-валидатор, при ошибке только warning, не блокирует. Все три
декларативны, ловят ошибки до облака.
команды
terraform planошибка precondition останавливает planterraform applypostcondition проверяется после refresh, до фиксации в stateконцепции