Skill

Generate High-Quality Terraform Modules

Skill for writing Terraform modules - standard structure, typed variables, for_each patterns, and provider constraints.

Works with githubhashicorp

Maintainer of this project? Claim this page to edit the listing.


91
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add 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

01

Write Terraform modules following standard structure and best practices.

02

Design input variables with validation and sensible defaults.

03

Implement resource naming, tagging, and conditional creation.

04

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.