lesson ── terraform-intermediate ── ~16 мин ── 5 шагов
Four utility providers that real-world HCL keeps reaching for: random
(unique names and passwords), time (timestamps and delays), archive
(packaging lambda code), external (calling any script). In this lesson
you use all four in one project. This is the usual mix of reusable
building blocks.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-utility
cat > lambda-src/handler.py <<'EOF'
def main(event, context):
return {"statusCode": 200, "body": "hello from terraformlab"}EOF
ls lambda-src/
Next, the Terraform part. In main.tf:
terraform { required_providers { archive = {source = "hashicorp/archive"
version = "~> 2.6"
}
random = {source = "hashicorp/random"
version = "~> 3.6"
}
time = {source = "hashicorp/time"
version = "~> 0.12"
}
}
}
data "archive_file" "lambda" {type = "zip"
source_file = "${path.module}/lambda-src/handler.py" output_path = "${path.module}/lambda.zip"}
Run init plus a plan:
terraform init
terraform plan
The plan shows that data.archive_file will be read. The provider
itself creates the lambda.zip file during apply.
✓ archive_file is in place. Now we add the rest of the providers.
Add this to main.tf:
resource "random_id" "fn_suffix" {byte_length = 4
}
resource "random_password" "api_key" {length = 24
special = false
}
resource "time_static" "created_at" {}locals { function_name = "linuxlab-utility-${random_id.fn_suffix.hex}" created_date = formatdate("YYYY-MM-DD", time_static.created_at.rfc3339)}
What happens here:
random_id.fn_suffix.hex, 8 hex characters, unique at creation,
stable between applies (written into state).random_password.api_key.result, a password of length 24, no special
characters. Marked sensitive automatically.time_static.created_at, pins a timestamp at the first apply. It does
not change after that.See tf-utility-providers.
✓ Random plus time are in place. Now we create the Lambda and put it on S3.
Add this to main.tf:
resource "aws_iam_role" "lambda" { name = "${local.function_name}-role" assume_role_policy = jsonencode({Version = "2012-10-17"
Statement = [{Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }}]
})
}
resource "aws_lambda_function" "demo" {function_name = local.function_name
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
handler = "handler.main"
runtime = "python3.12"
role = aws_iam_role.lambda.arn
environment { variables = {API_KEY = random_password.api_key.result
CREATED_DATE = local.created_date
}
}
tags = {CreatedAt = local.created_date
}
}
output "function_name" {value = aws_lambda_function.demo.function_name
}
The key points:
source_code_hash = data.archive_file.lambda.output_base64sha256
triggers a Lambda update when the code changes. Without it, Terraform
does not notice edits in handler.py.random_password.api_key.result goes into the Lambda env. It is in
plain text in state.local.created_date, a local computed from time_static.terraform apply -auto-approve
✓ The Lambda is created with a unique name and a timestamp.
Let's show that source_code_hash works by changing the handler code.
cat > lambda-src/handler.py <<'EOF'
def main(event, context):
return {"statusCode": 200, "body": "hello v2"}EOF
terraform plan
The plan shows:
# aws_lambda_function.demo will be updated in-place
~ source_code_hash = "..." -> "..."
Terraform saw that the hash changed (because the code changed) and
wants to update the Lambda. Without source_code_hash it would not
notice, since the filename is the same.
terraform apply -auto-approve
The Lambda is redeployed with the new code.
✓ The Lambda is updated. archive_file plus source_code_hash works.
OpenTofu keeps the CLI and state compatible with Terraform for the
commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On a first switch, though, back
up the state and do a run on a feature branch, the differences
cluster in the newer features (variables in backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
Sometimes you want a new suffix, a new bucket, everything fresh. Without an HCL change, random_id stays in state and does not change. Force it:
terraform apply -auto-approve -replace=random_id.fn_suffix
The plan shows:
# random_id.fn_suffix will be replaced (due to -replace)
And in a cascade, every resource that depends on it (the Lambda, the IAM role, because the name contains random_id.fn_suffix.hex):
# aws_iam_role.lambda will be destroyed
# aws_iam_role.lambda will be created (new name)
...
The apply goes through, and everything is recreated with the new suffix. This is the classic case of a stale pet name that you want fresh.
✓ Regenerating random worked in a cascade.
Sometimes you need to read a value from a source that is not among the AWS data sources: a git commit hash, a secret from an internal store, the result of an HTTP request with auth.
terraform { required_providers { external = {source = "hashicorp/external"
version = "~> 2.3"
}
}
}
data "external" "build_info" { program = ["bash", "${path.module}/scripts/build-info.sh"]}
resource "aws_lambda_function" "demo" {# ...
tags = {GitCommit = data.external.build_info.result.commit
}
}
The build-info.sh script:
#!/usr/bin/env bash
set -euo pipefail
commit=$(git -C / rev-parse HEAD 2>/dev/null || echo "unknown")
jq -n --arg c "$commit" '{commit: $c}'The important parts:
tonumber(data.external.x.result.count).See tf-archive-external-http for all three data providers.
random, unique IDs and passwords (sensitive in state). time,
timestamps and delays. archive_file, packaging files into a zip for
Lambda and Layers. external, run an arbitrary script and exchange
JSON. All four are HashiCorp-official, stable, with no dependency on the
AWS API.
команды
terraform apply -replace=random_id.suffixforced regeneration: the cascade recreates everything that depends on itterraform state show random_password.userthe password is visible in state: sensitive only in the outputterraform planexternal/archive are computed on every plan: keep that in mindконцепции