Claude Code Plugins

Community-maintained marketplace

Feedback

terraform-module-writer

@dengineproblem/agents-monorepo
0
0

Эксперт по Terraform модулям. Используй для создания переиспользуемых модулей, variables, outputs и best practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name terraform-module-writer
description Эксперт по Terraform модулям. Используй для создания переиспользуемых модулей, variables, outputs и best practices.

Terraform Module Writer

Эксперт по созданию высококачественных, переиспользуемых Terraform модулей с соблюдением industry best practices.

Module Structure

Standard Directory Layout

modules/
└── my-module/
    ├── main.tf           # Primary resource definitions
    ├── variables.tf      # Input variable declarations
    ├── outputs.tf        # Output value definitions
    ├── versions.tf       # Provider version constraints
    ├── locals.tf         # Local values and computations
    ├── data.tf           # Data source definitions
    ├── README.md         # Module documentation
    ├── CHANGELOG.md      # Version history
    ├── examples/
    │   ├── basic/
    │   │   ├── main.tf
    │   │   └── outputs.tf
    │   └── advanced/
    │       ├── main.tf
    │       └── outputs.tf
    └── tests/
        └── module_test.go

versions.tf Template

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0.0, < 6.0.0"
    }
    random = {
      source  = "hashicorp/random"
      version = ">= 3.5.0"
    }
  }
}

Input Variable Design

Variable Declaration Best Practices

# variables.tf

# Required variables (no default)
variable "name" {
  description = "Name prefix for all resources created by this module"
  type        = string

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]*$", var.name))
    error_message = "Name must start with a letter and contain only lowercase letters, numbers, and hyphens."
  }
}

variable "environment" {
  description = "Environment name (e.g., dev, staging, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be one of: dev, staging, prod."
  }
}

# Optional variables with sensible defaults
variable "instance_type" {
  description = "EC2 instance type for the application servers"
  type        = string
  default     = "t3.medium"

  validation {
    condition     = can(regex("^[a-z][0-9][.][a-z]+$", var.instance_type))
    error_message = "Instance type must be a valid AWS instance type format."
  }
}

variable "enable_monitoring" {
  description = "Enable detailed monitoring for resources"
  type        = bool
  default     = true
}

variable "min_instances" {
  description = "Minimum number of instances in the Auto Scaling Group"
  type        = number
  default     = 2

  validation {
    condition     = var.min_instances >= 1 && var.min_instances <= 100
    error_message = "Minimum instances must be between 1 and 100."
  }
}

variable "max_instances" {
  description = "Maximum number of instances in the Auto Scaling Group"
  type        = number
  default     = 10

  validation {
    condition     = var.max_instances >= 1 && var.max_instances <= 100
    error_message = "Maximum instances must be between 1 and 100."
  }
}

Complex Variable Types

# Object type with optional attributes
variable "vpc_config" {
  description = "VPC configuration for the module"
  type = object({
    vpc_id             = string
    subnet_ids         = list(string)
    security_group_ids = optional(list(string), [])
    assign_public_ip   = optional(bool, false)
  })

  validation {
    condition     = length(var.vpc_config.subnet_ids) >= 2
    error_message = "At least 2 subnet IDs required for high availability."
  }
}

# Map with specific value types
variable "tags" {
  description = "Additional tags to apply to all resources"
  type        = map(string)
  default     = {}

  validation {
    condition     = alltrue([for k, v in var.tags : can(regex("^[a-zA-Z][a-zA-Z0-9:/_-]*$", k))])
    error_message = "Tag keys must start with a letter and contain only valid characters."
  }
}

# List of objects
variable "ingress_rules" {
  description = "List of ingress rules for the security group"
  type = list(object({
    description = string
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = optional(list(string), [])
    security_groups = optional(list(string), [])
  }))
  default = []

  validation {
    condition = alltrue([
      for rule in var.ingress_rules :
      rule.from_port >= 0 && rule.from_port <= 65535 &&
      rule.to_port >= 0 && rule.to_port <= 65535
    ])
    error_message = "Port numbers must be between 0 and 65535."
  }
}

# Sensitive variables
variable "database_password" {
  description = "Master password for the RDS instance"
  type        = string
  sensitive   = true

  validation {
    condition     = length(var.database_password) >= 16
    error_message = "Database password must be at least 16 characters."
  }
}

Local Values

locals.tf Template

locals {
  # Naming convention
  name_prefix = "${var.name}-${var.environment}"

  # Common tags applied to all resources
  common_tags = merge(
    var.tags,
    {
      Name        = local.name_prefix
      Environment = var.environment
      ManagedBy   = "terraform"
      Module      = "my-module"
      Project     = var.name
    }
  )

  # Computed values
  is_production      = var.environment == "prod"
  enable_encryption  = local.is_production
  backup_retention   = local.is_production ? 30 : 7
  instance_count     = local.is_production ? var.max_instances : var.min_instances

  # Derived configurations
  monitoring_config = {
    enabled         = var.enable_monitoring || local.is_production
    detailed        = local.is_production
    retention_days  = local.is_production ? 90 : 30
    alarm_threshold = local.is_production ? 80 : 90
  }

  # Dynamic block helpers
  ingress_rules_map = {
    for idx, rule in var.ingress_rules :
    "${rule.protocol}-${rule.from_port}-${rule.to_port}" => rule
  }

  # Conditional resource naming
  bucket_name = var.bucket_name != null ? var.bucket_name : "${local.name_prefix}-storage-${random_id.bucket.hex}"
}

Resource Definitions

main.tf Template

# main.tf

#------------------------------------------------------------------------------
# Security Group
#------------------------------------------------------------------------------
resource "aws_security_group" "this" {
  name_prefix = "${local.name_prefix}-sg-"
  description = "Security group for ${var.name} ${var.environment}"
  vpc_id      = var.vpc_config.vpc_id

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-sg"
  })

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "ingress" {
  for_each = local.ingress_rules_map

  type              = "ingress"
  security_group_id = aws_security_group.this.id
  description       = each.value.description
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  protocol          = each.value.protocol
  cidr_blocks       = length(each.value.cidr_blocks) > 0 ? each.value.cidr_blocks : null
  source_security_group_id = length(each.value.security_groups) > 0 ? each.value.security_groups[0] : null
}

resource "aws_security_group_rule" "egress" {
  type              = "egress"
  security_group_id = aws_security_group.this.id
  description       = "Allow all outbound traffic"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}

#------------------------------------------------------------------------------
# Launch Template
#------------------------------------------------------------------------------
resource "aws_launch_template" "this" {
  name_prefix   = "${local.name_prefix}-lt-"
  description   = "Launch template for ${var.name} ${var.environment}"
  image_id      = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type

  network_interfaces {
    associate_public_ip_address = var.vpc_config.assign_public_ip
    security_groups             = concat([aws_security_group.this.id], var.vpc_config.security_group_ids)
    delete_on_termination       = true
  }

  iam_instance_profile {
    arn = aws_iam_instance_profile.this.arn
  }

  monitoring {
    enabled = local.monitoring_config.detailed
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"  # IMDSv2
    http_put_response_hop_limit = 1
  }

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
      volume_size           = var.root_volume_size
      volume_type           = "gp3"
      encrypted             = local.enable_encryption
      kms_key_id            = local.enable_encryption ? var.kms_key_id : null
      delete_on_termination = true
    }
  }

  tag_specifications {
    resource_type = "instance"
    tags          = local.common_tags
  }

  tag_specifications {
    resource_type = "volume"
    tags          = local.common_tags
  }

  tags = local.common_tags

  lifecycle {
    create_before_destroy = true
  }
}

#------------------------------------------------------------------------------
# Auto Scaling Group
#------------------------------------------------------------------------------
resource "aws_autoscaling_group" "this" {
  name_prefix         = "${local.name_prefix}-asg-"
  desired_capacity    = var.desired_instances
  min_size            = var.min_instances
  max_size            = var.max_instances
  vpc_zone_identifier = var.vpc_config.subnet_ids
  health_check_type   = "ELB"
  health_check_grace_period = 300

  launch_template {
    id      = aws_launch_template.this.id
    version = "$Latest"
  }

  dynamic "tag" {
    for_each = local.common_tags
    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [desired_capacity]
  }
}

Data Sources

data.tf Template

# data.tf

# Get current AWS account info
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_partition" "current" {}

# Get latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }
}

# Get VPC info if not provided
data "aws_vpc" "selected" {
  id = var.vpc_config.vpc_id
}

# Get subnet info for AZ distribution
data "aws_subnet" "selected" {
  for_each = toset(var.vpc_config.subnet_ids)
  id       = each.value
}

# Get available AZs
data "aws_availability_zones" "available" {
  state = "available"
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

Output Definitions

outputs.tf Template

# outputs.tf

#------------------------------------------------------------------------------
# Primary Outputs
#------------------------------------------------------------------------------
output "id" {
  description = "The unique identifier for this module instance"
  value       = aws_autoscaling_group.this.id
}

output "name" {
  description = "The name prefix used for all resources"
  value       = local.name_prefix
}

output "arn" {
  description = "ARN of the Auto Scaling Group"
  value       = aws_autoscaling_group.this.arn
}

#------------------------------------------------------------------------------
# Security Group Outputs
#------------------------------------------------------------------------------
output "security_group_id" {
  description = "ID of the security group created for this module"
  value       = aws_security_group.this.id
}

output "security_group_arn" {
  description = "ARN of the security group"
  value       = aws_security_group.this.arn
}

output "security_group_name" {
  description = "Name of the security group"
  value       = aws_security_group.this.name
}

#------------------------------------------------------------------------------
# Launch Template Outputs
#------------------------------------------------------------------------------
output "launch_template_id" {
  description = "ID of the launch template"
  value       = aws_launch_template.this.id
}

output "launch_template_arn" {
  description = "ARN of the launch template"
  value       = aws_launch_template.this.arn
}

output "launch_template_latest_version" {
  description = "Latest version of the launch template"
  value       = aws_launch_template.this.latest_version
}

#------------------------------------------------------------------------------
# Computed/Derived Outputs
#------------------------------------------------------------------------------
output "ami_id" {
  description = "ID of the AMI used for instances"
  value       = data.aws_ami.amazon_linux.id
}

output "availability_zones" {
  description = "List of availability zones where resources are deployed"
  value       = distinct([for s in data.aws_subnet.selected : s.availability_zone])
}

output "configuration" {
  description = "Summary of the module configuration"
  value = {
    environment       = var.environment
    instance_type     = var.instance_type
    min_instances     = var.min_instances
    max_instances     = var.max_instances
    monitoring_enabled = local.monitoring_config.enabled
    encryption_enabled = local.enable_encryption
  }
}

#------------------------------------------------------------------------------
# Sensitive Outputs
#------------------------------------------------------------------------------
output "iam_role_arn" {
  description = "ARN of the IAM role attached to instances"
  value       = aws_iam_role.this.arn
  sensitive   = false
}

Conditional Resource Creation

Using count vs for_each

# Use count for simple on/off toggles
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
  count = var.enable_monitoring ? 1 : 0

  alarm_name          = "${local.name_prefix}-high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 300
  statistic           = "Average"
  threshold           = local.monitoring_config.alarm_threshold
  alarm_description   = "CPU utilization exceeded threshold"
  alarm_actions       = var.alarm_actions

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.this.name
  }

  tags = local.common_tags
}

# Use for_each for collections
resource "aws_s3_bucket" "logs" {
  for_each = var.enable_logging ? toset(["access", "audit", "error"]) : toset([])

  bucket = "${local.name_prefix}-${each.key}-logs"

  tags = merge(local.common_tags, {
    LogType = each.key
  })
}

# for_each with complex objects
resource "aws_route53_record" "this" {
  for_each = {
    for record in var.dns_records :
    "${record.name}-${record.type}" => record
  }

  zone_id = var.route53_zone_id
  name    = each.value.name
  type    = each.value.type
  ttl     = each.value.ttl
  records = each.value.records
}

Module Composition

Root Module Example

# examples/complete/main.tf

terraform {
  required_version = ">= 1.5.0"
}

provider "aws" {
  region = var.region
}

# Network module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "${var.name}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["${var.region}a", "${var.region}b", "${var.region}c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = var.environment != "prod"

  tags = var.tags
}

# Application module
module "app" {
  source = "../../"

  name        = var.name
  environment = var.environment

  vpc_config = {
    vpc_id     = module.vpc.vpc_id
    subnet_ids = module.vpc.private_subnets
  }

  instance_type     = var.instance_type
  min_instances     = var.min_instances
  max_instances     = var.max_instances
  enable_monitoring = true

  ingress_rules = [
    {
      description = "HTTP from ALB"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      security_groups = [module.alb.security_group_id]
    }
  ]

  tags = var.tags
}

# ALB module
module "alb" {
  source  = "terraform-aws-modules/alb/aws"
  version = "~> 8.0"

  name               = "${var.name}-alb"
  load_balancer_type = "application"
  vpc_id             = module.vpc.vpc_id
  subnets            = module.vpc.public_subnets

  target_groups = [
    {
      name             = "${var.name}-tg"
      backend_protocol = "HTTP"
      backend_port     = 80
      target_type      = "instance"
    }
  ]

  tags = var.tags
}

# Outputs
output "vpc_id" {
  value = module.vpc.vpc_id
}

output "app_security_group_id" {
  value = module.app.security_group_id
}

output "alb_dns_name" {
  value = module.alb.lb_dns_name
}

Documentation

README.md Template

# Module Name

Brief description of what this module creates.

## Features

- Feature 1
- Feature 2
- Feature 3

## Usage

### Basic

```hcl
module "example" {
  source = "github.com/org/terraform-aws-module?ref=v1.0.0"

  name        = "my-app"
  environment = "prod"

  vpc_config = {
    vpc_id     = "vpc-12345678"
    subnet_ids = ["subnet-1", "subnet-2"]
  }
}

Advanced

module "example" {
  source = "github.com/org/terraform-aws-module?ref=v1.0.0"

  name        = "my-app"
  environment = "prod"

  vpc_config = {
    vpc_id             = "vpc-12345678"
    subnet_ids         = ["subnet-1", "subnet-2"]
    security_group_ids = ["sg-existing"]
    assign_public_ip   = false
  }

  instance_type     = "t3.large"
  min_instances     = 3
  max_instances     = 10
  enable_monitoring = true

  ingress_rules = [
    {
      description = "HTTPS"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["10.0.0.0/8"]
    }
  ]

  tags = {
    Team    = "platform"
    CostCenter = "12345"
  }
}

Requirements

Name Version
terraform >= 1.5.0
aws >= 5.0.0, < 6.0.0

Providers

Name Version
aws >= 5.0.0

Inputs

Name Description Type Default Required
name Name prefix for resources string n/a yes
environment Environment (dev/staging/prod) string n/a yes
vpc_config VPC configuration object n/a yes
instance_type EC2 instance type string "t3.medium" no
min_instances Minimum ASG size number 2 no
max_instances Maximum ASG size number 10 no

Outputs

Name Description
id Auto Scaling Group ID
security_group_id Security Group ID
launch_template_id Launch Template ID

License

MIT


---

## Лучшие практики

1. **Используй версионирование** — семантическое версионирование для модулей
2. **Валидируй входные переменные** — используй validation blocks
3. **Документируй всё** — описания для variables и outputs
4. **Избегай hardcoded значений** — всё должно быть configurable
5. **Используй for_each вместо count** — лучше управление state
6. **Группируй связанные ресурсы** — логическая организация main.tf
7. **Тестируй модули** — используй terratest или terraform test
8. **Следуй naming conventions** — консистентное именование ресурсов