bookworm-smart-assistant/skills/terraform-engineer/references/module-patterns.md

298 lines
6.1 KiB
Markdown

# Terraform Module Patterns
## Module Structure
```
terraform-aws-vpc/
├── main.tf # Primary resource definitions
├── variables.tf # Input variable declarations
├── outputs.tf # Output value definitions
├── versions.tf # Provider version constraints
├── README.md # Module documentation
├── examples/
│ └── complete/
│ ├── main.tf
│ └── variables.tf
└── tests/
└── vpc_test.go
```
## Basic Module Pattern
**main.tf**
```hcl
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
var.tags,
{
Name = var.name
}
)
}
resource "aws_subnet" "private" {
for_each = var.private_subnets
vpc_id = aws_vpc.this.id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
tags = merge(
var.tags,
{
Name = "${var.name}-private-${each.key}"
Type = "private"
}
)
}
```
**variables.tf**
```hcl
variable "name" {
description = "Name prefix for all resources"
type = string
validation {
condition = length(var.name) > 0 && length(var.name) <= 32
error_message = "Name must be 1-32 characters"
}
}
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "Must be valid IPv4 CIDR block"
}
}
variable "private_subnets" {
description = "Map of private subnet configurations"
type = map(object({
cidr_block = string
az = string
}))
default = {}
}
variable "tags" {
description = "Common tags for all resources"
type = map(string)
default = {}
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames in VPC"
type = bool
default = true
}
variable "enable_dns_support" {
description = "Enable DNS support in VPC"
type = bool
default = true
}
```
**outputs.tf**
```hcl
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = { for k, v in aws_subnet.private : k => v.id }
}
output "private_subnet_cidrs" {
description = "CIDR blocks of private subnets"
value = { for k, v in aws_subnet.private : k => v.cidr_block }
}
```
**versions.tf**
```hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
```
## Module Composition
```hcl
# Composite module using child modules
module "networking" {
source = "./modules/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
private_subnets = {
app1 = { cidr_block = "10.0.1.0/24", az = "us-east-1a" }
app2 = { cidr_block = "10.0.2.0/24", az = "us-east-1b" }
}
tags = local.common_tags
}
module "security" {
source = "./modules/security-groups"
vpc_id = module.networking.vpc_id
security_groups = {
web = {
ingress = [
{ from_port = 443, to_port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] }
]
}
}
}
```
## Dynamic Blocks
```hcl
resource "aws_security_group" "this" {
name = var.name
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
dynamic "egress" {
for_each = var.egress_rules
content {
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = egress.value.cidr_blocks
description = egress.value.description
}
}
}
```
## Conditional Resources
```hcl
# Create NAT gateway only if enabled
resource "aws_nat_gateway" "this" {
count = var.enable_nat_gateway ? 1 : 0
allocation_id = aws_eip.nat[0].id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.name}-nat"
}
depends_on = [aws_internet_gateway.this]
}
# Use for_each for multiple optional resources
resource "aws_route53_zone" "private" {
for_each = var.create_private_zone ? { main = var.domain_name } : {}
name = each.value
vpc {
vpc_id = aws_vpc.this.id
}
}
```
## Module Versioning
```hcl
# Pin to specific version
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
# ... configuration
}
# Use version constraints
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0" # >= 19.0, < 20.0
# ... configuration
}
# Reference Git tags
module "custom" {
source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.3"
# ... configuration
}
```
## Module Testing Example
```hcl
# examples/complete/main.tf
module "vpc_test" {
source = "../.."
name = "test-vpc"
cidr_block = "10.100.0.0/16"
private_subnets = {
app = { cidr_block = "10.100.1.0/24", az = "us-east-1a" }
}
tags = {
Environment = "test"
ManagedBy = "terraform"
}
}
output "vpc_id" {
value = module.vpc_test.vpc_id
}
```
## Best Practices
- Keep modules focused and single-purpose
- Use `for_each` over `count` for resources
- Validate all inputs with validation blocks
- Document all variables and outputs
- Use semantic versioning (MAJOR.MINOR.PATCH)
- Provide complete examples
- Test modules before publishing
- Use consistent naming conventions
- Tag all taggable resources
- Avoid hardcoded values