💻 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.

0.0
0Reviews
P
August 24, 2026

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

Reviews (0)

Please login to leave a review.
Loading reviews...