lesson ── terraform-production ── ~14 мин ── 5 шагов
Apply-тесты дорогие, даже LocalStack секунды на каждый запуск. Mock-провайдеры
заменяют реальный AWS на синтезированные ответы; command = apply
становится быстрым и offline. mock_provider, override_resource,
override_data, три инструмента. С Terraform 1.7+.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Для иллюстрации mock'а с defaults нужен модуль, где один ресурс ссылается на другой через id.
cd /home/student/tf-mocks/modules/audited-bucket
cat > main.tf <<'EOF'
resource "aws_s3_bucket" "this" {bucket = var.name
}
resource "aws_s3_bucket_versioning" "this" {bucket = aws_s3_bucket.this.id
versioning_configuration {status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "this" {bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
EOF
cat > variables.tf <<'EOF'
variable "name" {type = string
}
EOF
cat > outputs.tf <<'EOF'
output "id" {value = aws_s3_bucket.this.id
}
EOF
Три ресурса: бакет, versioning, public-access-block. Versioning и
ACL ссылаются на aws_s3_bucket.this.id.
✓ Модуль с cross-references готов.
cat > /home/student/tf-mocks/modules/audited-bucket/tests/mock_bare.tftest.hcl <<'EOF'
mock_provider "aws" {}run "with_bare_mock" {command = apply
variables {name = "test-mocked"
}
assert {condition = aws_s3_bucket.this.bucket == "test-mocked"
error_message = "bucket name not propagated"
}
}
EOF
cd /home/student/tf-mocks/modules/audited-bucket
terraform init -backend=false
terraform test -filter=tests/mock_bare.tftest.hcl
Должно показать pass. mock_provider "aws" {}, генерит дефолты
по schema, никакого облака. Apply, мгновенный.
✓ Bare mock работает. Один тест без LocalStack.
Голый mock возвращает "foo" для всех string-атрибутов. Если два
ресурса ссылаются на aws_s3_bucket.this.id, оба получают "foo"
это окей, но непрозрачно. Зафиксируем явный id:
cat > /home/student/tf-mocks/modules/audited-bucket/tests/mock_with_defaults.tftest.hcl <<'EOF'
mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = {id = "mocked-id-001"
arn = "arn:aws:s3:::mocked-id-001"
}
}
}
run "versioning_references_correct_bucket" {command = apply
variables {name = "test-with-defaults"
}
assert {condition = aws_s3_bucket_versioning.this.bucket == "mocked-id-001"
error_message = "versioning should reference bucket id mocked-id-001"
}
assert {condition = aws_s3_bucket_public_access_block.this.bucket == "mocked-id-001"
error_message = "PAB should reference bucket id mocked-id-001"
}
}
EOF
terraform test -filter=tests/mock_with_defaults.tftest.hcl
Должно показать pass. Versioning и PAB корректно ссылаются на
mocked-id-001, потому что mock зафиксировал это для bucket'а.
✓ Cross-resource references проверены без облака.
Иногда базовые mock-defaults подходят для большинства run'ов, но
в одном, другое значение. override_resource внутри run'а
приоритетнее, чем file-level mock.
cat > /home/student/tf-mocks/modules/audited-bucket/tests/override.tftest.hcl <<'EOF'
mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = {id = "default-id"
}
}
}
run "default_mock_used" {command = apply
variables { name = "x" } assert {condition = aws_s3_bucket_versioning.this.bucket == "default-id"
error_message = "expected default-id"
}
}
run "override_for_this_run" {command = apply
variables { name = "y" } override_resource {target = aws_s3_bucket.this
values = {id = "specific-override-id"
}
}
assert {condition = aws_s3_bucket_versioning.this.bucket == "specific-override-id"
error_message = "expected override id"
}
}
EOF
terraform test -filter=tests/override.tftest.hcl
Должно показать 2 passed. Первый run использует file-level default, второй, свой override. Это полезно для «общий тест + специальные кейсы», без дублирования mock-конфига.
✓ Mock и override работают в связке.
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 для полной матрицы.
Кратко покажем разницу. Запусти все mock-тесты с time:
time terraform test \
-filter=tests/mock_bare.tftest.hcl \
-filter=tests/mock_with_defaults.tftest.hcl \
-filter=tests/override.tftest.hcl
Записывай time real (например 1.2s).
Создай эквивалентный non-mock тест:
cat > tests/realstack.tftest.hcl <<'EOF'
provider "aws" {region = "us-east-1"
access_key = "test"
secret_key = "test"
s3_use_path_style = true
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {s3 = "http://localstack:4566"
iam = "http://localstack:4566"
sts = "http://localstack:4566"
}
}
run "real_apply" {command = apply
variables { name = "real-test-bucket" } assert {condition = aws_s3_bucket.this.id == "real-test-bucket"
error_message = "id mismatch"
}
}
EOF
time terraform test -filter=tests/realstack.tftest.hcl
Сравни, LocalStack дольше из-за реального provider'а и HTTP-вызовов. Mock в 5-20 раз быстрее. На большом test-suite разница, минуты.
Это то, ради чего mock'и существуют. В CI: основная масса unit-тестов на mock'ах; пара integration-тестов на LocalStack или AWS.
✓ Mock vs real: разница ощутима. Mock, основная масса CI, LocalStack, выборочно.
Mock vs real, не догма. Случаи когда mock проигрывает:
Тест на корректность HCL → реальный API. Например,
aws_iam_policy_document, это data-source, локально рендерит
JSON. Mock вернёт placeholder, реальный data-source, настоящую
политику. Если ассерт length(jsondecode(...)) == 5, нужен
реальный.
Cross-resource через computed-атрибут. Если ресурс A
возвращает computed arn нестандартной формы, и B парсит её
mock возвращает "foo", B не парсится, тест ложно валится.
Override спасает, но это бойлерплейт.
Тестируешь сам provider. Mock проверяет «HCL правильно описывает то, что хочешь». Если хочешь убедиться, что AWS принимает твой config, нужен реальный.
Правило: mock для «тест на свой код», real для «тест на интеграцию с облаком».
mock_provider "aws" {} в *.tftest.hcl подменяет провайдера на
fake. mock_resource "X" { defaults = {...} }, задаёт значения для
конкретного типа. override_resource / override_data, точечная
подмена в одном run-блоке.
команды
terraform test -filter=tests/mocked.tftest.hclmock-тесты быстрее, все apply'ы offline.terraform test -verboseвидишь какие override применились.концепции