Agent skill
module-patterns
Terraform module development patterns and best practices. Provides structure, versioning, and output scaffolds. Use when creating reusable modules.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/module-patterns-lgbarn-terraform-aws-eks
SKILL.md
Module Patterns
Terraform module development patterns and conventions.
Module Structure
Standard Layout
modules/
└── <module-name>/
├── main.tf # Primary resources
├── variables.tf # Input variables
├── outputs.tf # Module outputs
├── versions.tf # Version constraints
├── locals.tf # Local values
├── data.tf # Data sources (optional)
├── README.md # Documentation
├── examples/
│ ├── basic/
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ └── complete/
│ ├── main.tf
│ ├── outputs.tf
│ └── README.md
└── tests/
├── basic.tftest.hcl
└── complete.tftest.hcl
versions.tf Pattern
hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
variables.tf Patterns
Required Variable
hcl
variable "project" {
description = "Project name used in resource naming"
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]*$", var.project))
error_message = "Project name must start with a letter and contain only lowercase letters, numbers, and hyphens."
}
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
Optional with Default
hcl
variable "instance_type" {
description = "EC2 instance type for compute resources"
type = string
default = "t3.medium"
}
variable "enable_encryption" {
description = "Enable encryption at rest for all supported resources"
type = bool
default = true
}
Complex Type with Defaults
hcl
variable "node_groups" {
description = "Map of EKS managed node group definitions"
type = map(object({
instance_types = list(string)
min_size = number
max_size = number
desired_size = number
disk_size = optional(number, 100)
disk_type = optional(string, "gp3")
capacity_type = optional(string, "ON_DEMAND")
labels = optional(map(string), {})
taints = optional(list(object({
key = string
value = string
effect = string
})), [])
}))
default = {}
}
Sensitive Variable
hcl
variable "database_password" {
description = "Database master password"
type = string
sensitive = true
validation {
condition = length(var.database_password) >= 16
error_message = "Database password must be at least 16 characters."
}
}
Tags Variable
hcl
variable "tags" {
description = "Additional tags to apply to all resources"
type = map(string)
default = {}
}
outputs.tf Patterns
Resource Identifiers
hcl
output "id" {
description = "The ID of the primary resource"
value = aws_resource.this.id
}
output "arn" {
description = "The ARN of the primary resource"
value = aws_resource.this.arn
}
Connection Information
hcl
output "endpoint" {
description = "Endpoint for connecting to the resource"
value = aws_resource.this.endpoint
}
output "security_group_id" {
description = "ID of the associated security group"
value = aws_security_group.this.id
}
Sensitive Outputs
hcl
output "connection_string" {
description = "Database connection string"
value = "postgres://${var.username}:${random_password.db.result}@${aws_db_instance.this.endpoint}/${var.database_name}"
sensitive = true
}
Conditional Outputs
hcl
output "cluster_endpoint" {
description = "EKS cluster endpoint (null if cluster not created)"
value = var.create_cluster ? module.eks[0].cluster_endpoint : null
}
output "private_subnets" {
description = "List of private subnet IDs"
value = var.create_vpc ? module.vpc[0].private_subnets : var.private_subnet_ids
}
locals.tf Patterns
Name Construction
hcl
locals {
name_prefix = "${var.project}-${var.environment}"
resource_names = {
cluster = "${local.name_prefix}-eks"
vpc = "${local.name_prefix}-vpc"
rds = "${local.name_prefix}-db"
}
}
Tag Merging
hcl
locals {
default_tags = {
Project = var.project
Environment = var.environment
Terraform = "true"
Module = "module-name"
}
tags = merge(local.default_tags, var.tags)
}
Configuration Defaults
hcl
locals {
node_group_defaults = {
instance_types = ["m6i.large", "m5.large"]
disk_size = 100
disk_type = "gp3"
capacity_type = "ON_DEMAND"
}
node_groups = {
for k, v in var.node_groups : k => merge(local.node_group_defaults, v)
}
}
Conditional Logic
hcl
locals {
create_kms_key = var.kms_key_arn == null
kms_key_arn = local.create_kms_key ? aws_kms_key.this[0].arn : var.kms_key_arn
azs = var.azs != null ? var.azs : slice(data.aws_availability_zones.available.names, 0, 3)
}
main.tf Patterns
Conditional Resource Creation
hcl
resource "aws_kms_key" "this" {
count = var.create_kms_key ? 1 : 0
description = "KMS key for ${local.name_prefix}"
deletion_window_in_days = 7
enable_key_rotation = true
tags = local.tags
}
For Each with Maps
hcl
resource "aws_subnet" "private" {
for_each = var.private_subnets
vpc_id = aws_vpc.this.id
availability_zone = each.value.az
cidr_block = each.value.cidr
tags = merge(local.tags, {
Name = "${local.name_prefix}-private-${each.key}"
Type = "private"
})
}
Lifecycle Rules
hcl
resource "aws_rds_cluster" "this" {
cluster_identifier = local.resource_names.rds
# ... configuration ...
lifecycle {
prevent_destroy = true
ignore_changes = [master_password]
}
}
Timeouts
hcl
resource "aws_eks_cluster" "this" {
name = local.resource_names.cluster
# ... configuration ...
timeouts {
create = "45m"
update = "60m"
delete = "30m"
}
}
Terraform Test Pattern
hcl
# tests/basic.tftest.hcl
provider "aws" {
region = "us-east-1"
}
variables {
project = "test"
environment = "dev"
}
run "validate_resources" {
command = plan
assert {
condition = aws_s3_bucket.this.bucket != null
error_message = "S3 bucket should be created"
}
assert {
condition = aws_s3_bucket.this.tags["Environment"] == "dev"
error_message = "Environment tag should be 'dev'"
}
}
run "validate_naming" {
command = plan
assert {
condition = can(regex("^test-dev-", aws_s3_bucket.this.bucket))
error_message = "Bucket name should follow naming convention"
}
}
run "validate_encryption" {
command = plan
variables {
enable_encryption = true
}
assert {
condition = length(aws_s3_bucket_server_side_encryption_configuration.this) > 0
error_message = "Encryption should be enabled"
}
}
README.md Template
markdown
# Module Name
Brief description of what this module creates.
## Usage
```hcl
module "example" {
source = "path/to/module"
project = "myapp"
environment = "prod"
# Additional configuration
}
Requirements
| Name | Version |
|---|---|
| terraform | >= 1.5.0 |
| aws | >= 5.0 |
Providers
| Name | Version |
|---|---|
| aws | >= 5.0 |
Resources
| Name | Type |
|---|---|
| aws_resource.name | resource |
| aws_data.name | data source |
Inputs
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| project | Project name | string |
n/a | yes |
| environment | Environment | string |
n/a | yes |
| tags | Additional tags | map(string) |
{} |
no |
Outputs
| Name | Description |
|---|---|
| id | Resource ID |
| arn | Resource ARN |
Examples
License
Apache 2.0 Licensed.
## Example basic/main.tf
```hcl
provider "aws" {
region = "us-east-1"
}
module "example" {
source = "../../"
project = "myapp"
environment = "dev"
}
output "id" {
value = module.example.id
}
Moved Blocks for Refactoring
hcl
# moves.tf - Use when renaming resources
moved {
from = aws_instance.web
to = aws_instance.application
}
moved {
from = aws_s3_bucket.data
to = module.storage.aws_s3_bucket.main
}
moved {
from = module.old_name
to = module.new_name
}
Didn't find tool you were looking for?