💻 Coding
Terraform Module Builder from Spec
Turn an infra spec into a reusable Terraform module: variables, outputs, resources, examples, and a README. No invented providers.
0Reviews
Prompt
Act as a Terraform reviewer who writes reusable modules. Only use providers and resources named in Inputs. Prefer variables with types and validation. Do not hide destroy-time surprises. Inputs: - Module name: [Name] - Cloud / provider: [Provider and version constraint] - Spec: [What it must create] - Required variables: [List] - Optional with defaults: [List] - Outputs consumers need: [List] - Constraints: [No public, tags, naming, etc.] - Terraform version: [Version] - Examples wanted: [minimal / prod-shaped / both] Generate: 1. Layout: versions.tf, variables.tf, main.tf, outputs.tf, README.md, examples/minimal, tests note. 2. versions.tf: required_version, required_providers with source and version from Inputs. Do not add extra providers. 3. variables.tf: type, description, nullable, validation blocks for names and enums. Sensitive where it is a secret. 4. main.tf: resources from Spec only. Count/for_each only when the spec is a set. Lifecycle notes for prevent_destroy if Constraints ask. 5. outputs.tf: values consumers need. Sensitive true if a secret. 6. README: what it creates, example call, required permissions (high level), destroy caveats. 7. examples/minimal: a tiny root module that calls it. No fake account IDs; use variables. 8. Checks: 5 terraform validate / plan mental checks (missing tag, empty name, wrong region, accidental public, count=0). Constraints: - No invented resources (no CloudFront if Spec is a bucket). - No hardcoded secrets or account IDs. - Tags via a map variable, merged, not copy-pasted on each resource unless Spec demands it. - If Spec is underspecified, list open questions instead of guessing SKUs.
Instructions
Replace every [bracket] with your details before running. Works on ChatGPT, Claude, and Gemini.
Examples
Example Input
Module name: s3_logs_bucket Cloud / provider: hashicorp/aws ~> 5.0 Spec: private S3 bucket for app logs, versioning on, public access blocked, server-side encryption AES256, lifecycle expire noncurrent versions after 90 days. No CloudFront. No website hosting. Required variables: name, tags Optional with defaults: force_destroy=false, noncurrent_expire_days=90 Outputs consumers need: bucket_id, bucket_arn, bucket_domain_name Constraints: never public; name must be lowercase; merge tags with Module=s3_logs_bucket; no prevent_destroy (dev accounts need to tear down) Terraform version: >= 1.6 Examples wanted: minimal
Example Output
1. Layout
s3_logs_bucket/versions.tf, variables.tf, main.tf, outputs.tf, README.md, examples/minimal/main.tf
No tests/ in-repo unless they already use terraform test. Note: add a terraform test later for public-access deny.
2. versions.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
3. variables.tf
variable "name" { type = string
validation { condition = var.name == lower(var.name) && length(var.name) > 2
error_message = "name must be lowercase and non-empty" } }
variable "tags" { type = map(string) }
variable "force_destroy" { type = bool, default = false }
variable "noncurrent_expire_days" { type = number, default = 90
validation { condition = var.noncurrent_expire_days >= 1
error_message = "expire days must be >= 1" } }
4. main.tf (spec only)
locals { tags = merge(var.tags, { Module = "s3_logs_bucket" }) }
resource "aws_s3_bucket" "this" { bucket = var.name
force_destroy = var.force_destroy
tags = local.tags }
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 }
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } }
resource "aws_s3_bucket_lifecycle_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule { id = "expire-noncurrent" status = "Enabled"
noncurrent_version_expiration { noncurrent_days = var.noncurrent_expire_days } } }
No website, no CloudFront, no bucket policy that grants *.
5. outputs.tf
output "bucket_id" { value = aws_s3_bucket.this.id }
output "bucket_arn" { value = aws_s3_bucket.this.arn }
output "bucket_domain_name" { value = aws_s3_bucket.this.bucket_domain_name }
6. README
Creates a private versioned logs bucket, AES256, public access block, noncurrent expire. Call with name + tags. Needs s3:CreateBucket and related on the account. Destroy: if objects exist, set force_destroy=true or empty first. No prevent_destroy, per Constraints.
7. examples/minimal
variable "name" { type = string }
module "logs" { source = "../.."
name = var.name
tags = { Env = "dev" } }
8. Checks
- empty name: validation fails
- mixed-case name: validation fails
- missing tags: required, plan fails
- public ACL attempt: not a variable, cannot. Do not add acl=public-read later.
- noncurrent_expire_days=0: validation fails