Files
James Bland befb8fbaeb feat: initial Claude Code configuration scaffold
Comprehensive Claude Code guidance system with:

- 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit
- 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#),
  infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD),
  testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability)
- 3 hooks: secret detection, auto-formatting, TDD git pre-commit
- Strict TDD enforcement with 80%+ coverage requirements
- Multi-model strategy: Opus for planning, Sonnet for execution (opusplan)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 15:47:34 -05:00

557 lines
12 KiB
Markdown

---
name: terraform-aws
description: Terraform Infrastructure as Code for AWS with module patterns, state management, and best practices. Use when writing Terraform configurations or planning AWS infrastructure.
---
# Terraform AWS Skill
## Project Structure
```
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── ecs-service/
│ ├── rds/
│ ├── lambda/
│ └── s3-bucket/
└── shared/
└── providers.tf
```
## Backend Configuration
### S3 Backend (recommended for AWS)
```hcl
# backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "environments/dev/terraform.tfstate"
region = "eu-west-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
```
### State Lock Table
```hcl
# One-time setup for state locking
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock"
Environment = "shared"
}
}
```
## Provider Configuration
```hcl
# providers.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
}
}
}
```
## Module Patterns
### VPC Module
```hcl
# modules/vpc/variables.tf
variable "name" {
description = "Name prefix for VPC resources"
type = string
}
variable "cidr_block" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
variable "enable_nat_gateway" {
description = "Enable NAT Gateway for private subnets"
type = bool
default = true
}
variable "single_nat_gateway" {
description = "Use single NAT Gateway (cost saving for non-prod)"
type = bool
default = false
}
# modules/vpc/main.tf
locals {
az_count = length(var.availability_zones)
public_subnets = [
for i, az in var.availability_zones :
cidrsubnet(var.cidr_block, 8, i)
]
private_subnets = [
for i, az in var.availability_zones :
cidrsubnet(var.cidr_block, 8, i + local.az_count)
]
}
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = var.name
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.name}-igw"
}
}
resource "aws_subnet" "public" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-${var.availability_zones[count.index]}"
Type = "public"
}
}
resource "aws_subnet" "private" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.private_subnets[count.index]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.name}-private-${var.availability_zones[count.index]}"
Type = "private"
}
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : local.az_count) : 0
domain = "vpc"
tags = {
Name = "${var.name}-nat-eip-${count.index}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : local.az_count) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.name}-nat-${count.index}"
}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}
```
### Module Usage
```hcl
# environments/dev/main.tf
module "vpc" {
source = "../../modules/vpc"
name = "${var.project_name}-${var.environment}"
cidr_block = "10.0.0.0/16"
availability_zones = ["eu-west-2a", "eu-west-2b", "eu-west-2c"]
enable_nat_gateway = true
single_nat_gateway = true # Cost saving for dev
}
module "ecs_cluster" {
source = "../../modules/ecs-cluster"
name = "${var.project_name}-${var.environment}"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
depends_on = [module.vpc]
}
```
## Security Best Practices
### Security Groups
```hcl
resource "aws_security_group" "app" {
name = "${var.name}-app-sg"
description = "Security group for application servers"
vpc_id = var.vpc_id
# Only allow inbound from load balancer
ingress {
description = "HTTP from ALB"
from_port = var.app_port
to_port = var.app_port
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
# Allow all outbound
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.name}-app-sg"
}
}
# NEVER do this - open to world
# ingress {
# from_port = 22
# to_port = 22
# protocol = "tcp"
# cidr_blocks = ["0.0.0.0/0"] # BAD!
# }
```
### IAM Roles with Least Privilege
```hcl
data "aws_iam_policy_document" "ecs_task_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ecs_task_role" {
name = "${var.name}-ecs-task-role"
assume_role_policy = data.aws_iam_policy_document.ecs_task_assume_role.json
}
# Specific permissions only
data "aws_iam_policy_document" "ecs_task_policy" {
statement {
sid = "AllowS3Access"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
]
resources = [
"${aws_s3_bucket.app_data.arn}/*"
]
}
statement {
sid = "AllowSecretsAccess"
effect = "Allow"
actions = [
"secretsmanager:GetSecretValue"
]
resources = [
aws_secretsmanager_secret.app_secrets.arn
]
}
}
```
### Secrets Management
```hcl
# NEVER hardcode secrets
# BAD:
# resource "aws_db_instance" "main" {
# password = "mysecretpassword" # NEVER!
# }
# GOOD: Use AWS Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.name}/db-password"
recovery_window_in_days = 7
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = jsonencode({
password = random_password.db.result
})
}
resource "random_password" "db" {
length = 32
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
# Reference in RDS
resource "aws_db_instance" "main" {
# ...
password = random_password.db.result
lifecycle {
ignore_changes = [password]
}
}
```
## Variables and Validation
```hcl
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
validation {
condition = can(regex("^t[23]\\.", var.instance_type))
error_message = "Only t2 and t3 instance types are allowed."
}
}
variable "tags" {
description = "Additional tags for resources"
type = map(string)
default = {}
}
```
## Data Sources
```hcl
# Get latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Get current AWS account ID
data "aws_caller_identity" "current" {}
# Get current region
data "aws_region" "current" {}
# Reference in resources
resource "aws_instance" "example" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
tags = {
Name = "example-${data.aws_region.current.name}"
}
}
```
## Outputs
```hcl
output "vpc_id" {
description = "ID of the VPC"
value = module.vpc.vpc_id
}
output "alb_dns_name" {
description = "DNS name of the Application Load Balancer"
value = aws_lb.main.dns_name
}
output "db_endpoint" {
description = "Endpoint of the RDS instance"
value = aws_db_instance.main.endpoint
sensitive = false
}
output "db_password_secret_arn" {
description = "ARN of the database password secret"
value = aws_secretsmanager_secret.db_password.arn
sensitive = true # Hide in logs
}
```
## Lifecycle Rules
```hcl
resource "aws_instance" "example" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
lifecycle {
# Prevent accidental destruction
prevent_destroy = true
# Create new before destroying old
create_before_destroy = true
# Ignore changes to tags made outside Terraform
ignore_changes = [
tags["LastModified"],
]
}
}
```
## Commands
```bash
# Initialize
terraform init
terraform init -upgrade # Upgrade providers
# Planning
terraform plan # Preview changes
terraform plan -out=tfplan # Save plan
terraform plan -target=module.vpc # Plan specific resource
# Applying
terraform apply # Apply changes
terraform apply tfplan # Apply saved plan
terraform apply -auto-approve # Skip confirmation (CI/CD only!)
# State management
terraform state list # List resources
terraform state show aws_vpc.main # Show specific resource
terraform import aws_vpc.main vpc-123 # Import existing resource
# Workspace (for multiple environments)
terraform workspace list
terraform workspace new staging
terraform workspace select dev
# Formatting and validation
terraform fmt -check # Check formatting
terraform fmt -recursive # Format all files
terraform validate # Validate configuration
```
## Anti-Patterns to Avoid
```hcl
# BAD: Hardcoded values
resource "aws_instance" "web" {
ami = "ami-12345678" # Will break across regions
instance_type = "t2.micro" # No flexibility
}
# GOOD: Use data sources and variables
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
}
# BAD: No state locking
terraform {
backend "s3" {
bucket = "my-state"
key = "terraform.tfstate"
# Missing dynamodb_table!
}
}
# BAD: Secrets in tfvars
# terraform.tfvars
db_password = "mysecret" # NEVER!
# GOOD: Use secrets manager or environment variables
# export TF_VAR_db_password=$(aws secretsmanager get-secret-value ...)
```