← Back to list

module-patterns
by lgbarn
Terraform/OpenTofu infrastructure development for AWS with EKS/Kubernetes support. Provides specialized agents for IaC development, security analysis, cost estimation, and state management.
⭐ 0🍴 0📅 Dec 23, 2025
SKILL.md
name: module-patterns description: Terraform module development patterns and best practices. Provides structure, versioning, and output scaffolds. Use when creating reusable modules.
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
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
variables.tf Patterns
Required Variable
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
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
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
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
variable "tags" {
description = "Additional tags to apply to all resources"
type = map(string)
default = {}
}
outputs.tf Patterns
Resource Identifiers
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
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
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
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
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
locals {
default_tags = {
Project = var.project
Environment = var.environment
Terraform = "true"
Module = "module-name"
}
tags = merge(local.default_tags, var.tags)
}
Configuration Defaults
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
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
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
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
resource "aws_rds_cluster" "this" {
cluster_identifier = local.resource_names.rds
# ... configuration ...
lifecycle {
prevent_destroy = true
ignore_changes = [master_password]
}
}
Timeouts
resource "aws_eks_cluster" "this" {
name = local.resource_names.cluster
# ... configuration ...
timeouts {
create = "45m"
update = "60m"
delete = "30m"
}
}
Terraform Test Pattern
# 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
# 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
# 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
}
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
3ヶ月以内に更新
+5
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
○言語
プログラミング言語が設定されている
0/5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
