Generate High-Quality Terraform Modules
Skill for writing Terraform modules - standard structure, typed variables, for_each patterns, and provider constraints.
Maintainer of this project? Claim this page to edit the listing.
1.0.0Add to Favorites
Why it matters
Automate the creation of reusable, maintainable, and testable Terraform modules. This asset adheres to industry best practices and HashiCorp guidelines, ensuring robust infrastructure as code.
Outcomes
What it gets done
Write Terraform modules following standard structure and best practices.
Design input variables with validation and sensible defaults.
Implement resource naming, tagging, and conditional creation.
Generate comprehensive outputs and documentation for module composition.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/vb-terraform-module-writer | bash Overview
Terraform Module Writer
A skill for Terraform modules - standard file structure, typed and validated input variables, for_each and data-source patterns, composable outputs, and pinned provider versions. Use it for authoring or reviewing a module's own code and structure, not Terraform state-backend setup or CI/CD pipeline configuration.
What it does
This skill writes high-quality, reusable Terraform modules following HashiCorp's official guidelines, favoring composition over inheritance. The standard module structure is main.tf (primary resources), variables.tf (input variables), outputs.tf (output values), versions.tf (provider requirements), a README.md, and an examples/ directory with working usage scenarios. Input-variable design covers descriptive names and descriptions, typed variables with object({...}) grouping related settings and optional(bool, true) defaults, and validation blocks such as checking a VPC CIDR block is valid via can(cidrhost(var.vpc_config.cidr_block, 0)).
Resource naming and tagging conventions use a local.name_prefix falling back to the project name, and a local.common_tags built by merging user-supplied tags with fixed ManagedBy/Module tags. Advanced patterns favor for_each over count for conditional resources and better state management - shown creating private subnets from a map built with { for idx, subnet in var.private_subnets : idx => subnet } - alongside count for simple boolean-gated resources like an internet gateway. Data-source lookups keep modules environment-agnostic, such as querying aws_availability_zones with an opt-in-status filter and falling back to a slice() of the discovered zones when none are explicitly configured. Output design provides both individual values (vpc_id, vpc_arn) and collections (a for expression producing private_subnet_ids, and a map of full private-subnet details) so modules compose cleanly. Provider configuration always pins version constraints in versions.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
Testing and validation guidance covers validation blocks on critical inputs (e.g., restricting an environment variable to dev/staging/prod), meaningful examples in the examples/ directory, and lifecycle { prevent_destroy = true } to guard against accidental deletion. Documentation best practices call for a comprehensive README covering all variables, outputs, and requirements, with multiple usage scenarios and provider-configuration examples. The five qualities every module should have: composable, configurable, consistent, documented, and tested.
When to use - and when NOT to
Use it when authoring or reviewing a reusable Terraform module targeting a modern Terraform baseline (required_version >= 1.0) and the hashicorp/aws provider at >= 5.0 - structuring files, designing typed and validated input variables, choosing between count and for_each, or pinning provider versions. It is not a guide to Terraform state-backend setup or CI/CD pipeline configuration - it is scoped to the module's own code and structure.
Inputs and outputs
Given an infrastructure component to modularize, it produces both simple count-gated resources - such as an aws_internet_gateway created only if var.enable_internet_gateway ? 1 : 0 - and for_each-driven resource sets like the private subnets, alongside typed and validated input variables and a pinned versions.tf.
Integrations
Written in HCL for Terraform, with examples targeting the hashicorp/aws provider.
Who it's for
Infrastructure and platform engineers writing or reviewing reusable Terraform modules.
Source README
Terraform Module Writer Expert
You are an expert in writing high-quality, reusable Terraform modules that follow industry best practices and HashiCorp's official guidelines. You create modules that are maintainable, testable, and follow the principle of composition over inheritance.
Module Structure and Organization
Always organize modules with this standard structure:
module-name/
├── main.tf # Primary resources
├── variables.tf # Input variables
├── outputs.tf # Output values
├── versions.tf # Provider requirements
├── README.md # Documentation
└── examples/ # Usage examples
└── basic/
├── main.tf
└── variables.tf
Core Principles
Input Variables Design
- Use descriptive names and include comprehensive descriptions
- Set appropriate types with validation rules
- Provide sensible defaults where possible
- Group related variables using objects
variable "vpc_config" {
description = "VPC configuration settings"
type = object({
cidr_block = string
enable_dns_hostnames = optional(bool, true)
enable_dns_support = optional(bool, true)
instance_tenancy = optional(string, "default")
})
validation {
condition = can(cidrhost(var.vpc_config.cidr_block, 0))
error_message = "VPC CIDR block must be a valid IPv4 CIDR."
}
}
variable "tags" {
description = "A map of tags to assign to resources"
type = map(string)
default = {}
}
Resource Naming and Tagging
- Use consistent naming patterns with prefixes/suffixes
- Implement comprehensive tagging strategies
- Support custom naming overrides
locals {
name_prefix = var.name_prefix != "" ? var.name_prefix : var.project_name
common_tags = merge(
var.tags,
{
ManagedBy = "terraform"
Module = "vpc"
}
)
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_config.cidr_block
enable_dns_hostnames = var.vpc_config.enable_dns_hostnames
enable_dns_support = var.vpc_config.enable_dns_support
instance_tenancy = var.vpc_config.instance_tenancy
tags = merge(
local.common_tags,
{
Name = "${local.name_prefix}-vpc"
}
)
}
Advanced Patterns
Conditional Resource Creation
- Use count or for_each for conditional resources
- Prefer for_each over count for better state management
resource "aws_internet_gateway" "main" {
count = var.enable_internet_gateway ? 1 : 0
vpc_id = aws_vpc.main.id
tags = merge(
local.common_tags,
{
Name = "${local.name_prefix}-igw"
}
)
}
resource "aws_subnet" "private" {
for_each = { for idx, subnet in var.private_subnets : idx => subnet }
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
tags = merge(
local.common_tags,
{
Name = "${local.name_prefix}-private-${each.key}"
Type = "private"
}
)
}
Data Sources and Lookups
- Use data sources to make modules flexible and environment-agnostic
- Implement fallback strategies
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
locals {
availability_zones = length(var.availability_zones) > 0 ? var.availability_zones : slice(data.aws_availability_zones.available.names, 0, min(3, length(data.aws_availability_zones.available.names)))
}
Output Design
- Provide comprehensive outputs for module composition
- Use descriptive names and include helpful descriptions
- Output both individual resources and collections
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_arn" {
description = "ARN of the VPC"
value = aws_vpc.main.arn
}
output "private_subnet_ids" {
description = "List of IDs of private subnets"
value = [for subnet in aws_subnet.private : subnet.id]
}
output "private_subnets" {
description = "Map of private subnet details"
value = {
for k, subnet in aws_subnet.private : k => {
id = subnet.id
arn = subnet.arn
cidr_block = subnet.cidr_block
availability_zone = subnet.availability_zone
}
}
}
Provider Configuration
- Always specify provider version constraints
- Use required_providers block in versions.tf
### versions.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
Testing and Validation
- Include validation blocks for critical inputs
- Create meaningful examples in the examples directory
- Use lifecycle rules to prevent accidental deletion
resource "aws_s3_bucket" "example" {
bucket = var.bucket_name
lifecycle {
prevent_destroy = true
}
tags = local.common_tags
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be one of: dev, staging, prod."
}
}
Documentation Best Practices
- Include comprehensive README.md with usage examples
- Document all variables, outputs, and requirements
- Provide multiple usage scenarios
- Include provider configuration examples
Always create modules that are:
- Composable: Can be combined with other modules
- Configurable: Accept parameters for customization
- Consistent: Follow naming and structure conventions
- Documented: Include clear usage instructions
- Tested: Provide working examples
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.