Claude Agent Skill · by Hashicorp

Refactor Module

Install Refactor Module skill for Claude Code from hashicorp/agent-skills.

Install
Terminal · npx
$npx skills add https://github.com/hashicorp/agent-skills --skill refactor-module
Works with Paperclip

How Refactor Module fits into a Paperclip company.

Refactor Module drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md538 lines
Expand
---name: refactor-moduledescription: Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.metadata:  copyright: Copyright IBM Corp. 2026  version: "0.0.1"--- # Skill: Refactor Module ## OverviewThis skill guides AI agents in transforming monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices. ## Capability StatementThe agent will analyze existing Terraform code and systematically refactor it into well-structured modules with:- Clear interface contracts (variables and outputs)- Proper encapsulation and abstraction- Versioning and documentation- Testing frameworks- Migration path for existing state ## Prerequisites- Existing Terraform configuration to refactor- Understanding of resource dependencies- Access to current state file (for migration planning)- Knowledge of module registry patterns ## Input Parameters | Parameter | Type | Required | Description ||-----------|------|----------|-------------|| `source_directory` | string | Yes | Path to existing Terraform configuration || `module_name` | string | Yes | Name for the new module || `abstraction_level` | string | No | "simple", "intermediate", "advanced" (default: intermediate) || `preserve_state` | boolean | Yes | Whether to maintain state compatibility || `target_registry` | string | No | Target module registry (local, private, public) | ## Execution Steps ### 1. Analysis Phase```markdown**Identify Refactoring Candidates**- Group resources by logical function- Identify repeated patterns- Map resource dependencies- Detect configuration coupling- Analyze variable usage patterns **Complexity Assessment**- Count resource relationships- Measure variable propagation depth- Identify cross-resource references- Evaluate state migration complexity``` ### 2. Module Design #### Interface Design```hcl# Define clear input contractvariable "network_config" {  description = "Network configuration parameters"  type = object({    cidr_block         = string    availability_zones = list(string)    enable_nat         = bool  })    validation {    condition     = can(cidrhost(var.network_config.cidr_block, 0))    error_message = "CIDR block must be valid IPv4 CIDR."  }} # Define output contractoutput "vpc_id" {  description = "ID of the created VPC"  value       = aws_vpc.main.id} output "private_subnet_ids" {  description = "List of private subnet IDs"  value       = { for k, v in aws_subnet.private : k => v.id }}``` #### Encapsulation Strategy```markdown**What to Include in Module:**- Tightly coupled resources (VPC + subnets)- Resources with shared lifecycle- Configuration with clear boundaries **What to Keep Separate:**- Cross-cutting concerns (monitoring, tagging)- Resources with different lifecycles- Provider-specific configurations``` ### 3. Code Transformation #### Before: Monolithic Configuration```hcl# main.tf (monolithic)resource "aws_vpc" "main" {  cidr_block = "10.0.0.0/16"  enable_dns_hostnames = true    tags = {    Name = "production-vpc"    Environment = "prod"  }} resource "aws_subnet" "public_1" {  vpc_id            = aws_vpc.main.id  cidr_block        = "10.0.1.0/24"  availability_zone = "us-east-1a"    tags = {    Name = "public-subnet-1"    Type = "public"  }} resource "aws_subnet" "public_2" {  vpc_id            = aws_vpc.main.id  cidr_block        = "10.0.2.0/24"  availability_zone = "us-east-1b"    tags = {    Name = "public-subnet-2"    Type = "public"  }} resource "aws_internet_gateway" "main" {  vpc_id = aws_vpc.main.id    tags = {    Name = "production-igw"  }} # ... more repetitive subnet and routing resources``` #### After: Modular Structure```hcl# modules/vpc/main.tflocals {  subnet_count = length(var.availability_zones)} resource "aws_vpc" "main" {  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" "public" {  for_each = var.create_public_subnets ? toset(var.availability_zones) : []    vpc_id                  = aws_vpc.main.id  cidr_block              = cidrsubnet(var.cidr_block, 8, index(var.availability_zones, each.value))  availability_zone       = each.value  map_public_ip_on_launch = true    tags = merge(    var.tags,    {      Name = "${var.name}-public-${each.value}"      Type = "public"    }  )} resource "aws_internet_gateway" "main" {  count  = var.create_public_subnets ? 1 : 0  vpc_id = aws_vpc.main.id    tags = merge(    var.tags,    {      Name = "${var.name}-igw"    }  )} # modules/vpc/variables.tfvariable "name" {  description = "Name prefix for all resources"  type        = string} variable "cidr_block" {  description = "CIDR block for the VPC"  type        = string    validation {    condition     = can(cidrhost(var.cidr_block, 0))    error_message = "Must be a valid IPv4 CIDR block."  }} variable "availability_zones" {  description = "List of availability zones"  type        = list(string)} variable "create_public_subnets" {  description = "Whether to create public subnets"  type        = bool  default     = true} variable "enable_dns_hostnames" {  description = "Enable DNS hostnames in the VPC"  type        = bool  default     = true} variable "enable_dns_support" {  description = "Enable DNS support in the VPC"  type        = bool  default     = true} variable "tags" {  description = "Tags to apply to all resources"  type        = map(string)  default     = {}} # modules/vpc/outputs.tfoutput "vpc_id" {  description = "ID of the VPC"  value       = aws_vpc.main.id} output "vpc_cidr_block" {  description = "CIDR block of the VPC"  value       = aws_vpc.main.cidr_block} output "public_subnet_ids" {  description = "Map of availability zones to public subnet IDs"  value       = { for k, v in aws_subnet.public : k => v.id }} output "internet_gateway_id" {  description = "ID of the internet gateway"  value       = try(aws_internet_gateway.main[0].id, null)} # Root configuration using modulemodule "vpc" {  source = "./modules/vpc"    name               = "production"  cidr_block         = "10.0.0.0/16"  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]    tags = {    Environment = "production"    ManagedBy   = "Terraform"  }}``` ### 4. State Migration #### Generate Migration Plan```hcl# migration.tf# Use moved blocks for state refactoring (Terraform 1.1+) moved {  from = aws_vpc.main  to   = module.vpc.aws_vpc.main} moved {  from = aws_subnet.public_1  to   = module.vpc.aws_subnet.public["us-east-1a"]} moved {  from = aws_subnet.public_2  to   = module.vpc.aws_subnet.public["us-east-1b"]} moved {  from = aws_internet_gateway.main  to   = module.vpc.aws_internet_gateway.main[0]}``` #### Manual State Migration (Pre-1.1)```bash# Generate state migration commandsterraform state mv aws_vpc.main module.vpc.aws_vpc.mainterraform state mv aws_subnet.public_1 'module.vpc.aws_subnet.public["us-east-1a"]'terraform state mv aws_subnet.public_2 'module.vpc.aws_subnet.public["us-east-1b"]'terraform state mv aws_internet_gateway.main 'module.vpc.aws_internet_gateway.main[0]'``` ### 5. Module Documentation ```markdown# VPC Module ## OverviewCreates a VPC with configurable public and private subnets across multiple availability zones. ## Features- Multi-AZ subnet deployment- Optional NAT gateway configuration- VPC Flow Logs integration- Customizable CIDR allocation ## Usage \`\`\`hclmodule "vpc" {  source = "./modules/vpc"    name               = "my-vpc"  cidr_block         = "10.0.0.0/16"  availability_zones = ["us-east-1a", "us-east-1b"]    create_public_subnets  = true  create_private_subnets = true  enable_nat_gateway     = true    tags = {    Environment = "production"  }}\`\`\` ## Requirements | Name | Version ||------|---------|| terraform | >= 1.5.0 || aws | ~> 5.0 | ## Inputs | Name | Description | Type | Default | Required ||------|-------------|------|---------|----------|| name | Name prefix for resources | `string` | n/a | yes || cidr_block | VPC CIDR block | `string` | n/a | yes || availability_zones | List of AZs | `list(string)` | n/a | yes | ## Outputs | Name | Description ||------|-------------|| vpc_id | VPC identifier || public_subnet_ids | Map of public subnet IDs || private_subnet_ids | Map of private subnet IDs | ## Examples See [examples/](./examples/) directory for complete usage examples.``` ### 6. Testing Use skill terraform-test **Test File**: A `.tftest.hcl` or `.tftest.json` file containing test configuration and run blocks that validate your Terraform configuration. **Test Block**: Optional configuration block that defines test-wide settings (available since Terraform 1.6.0). **Run Block**: Defines a single test scenario with optional variables, provider configurations, and assertions. Each test file requires at least one run block. **Assert Block**: Contains conditions that must evaluate to true for the test to pass. Failed assertions cause the test to fail. **Mock Provider**: Simulates provider behavior without creating real infrastructure (available since Terraform 1.7.0). **Test Modes**: Tests run in apply mode (default, creates real infrastructure) or plan mode (validates logic without creating resources). #### File Structure Terraform test files use the `.tftest.hcl` or `.tftest.json` extension and are typically organized in a `tests/` directory. Use clear naming conventions to distinguish between unit tests (plan mode) and integration tests (apply mode): ```my-module/├── main.tf├── variables.tf├── outputs.tf└── tests/    ├── unit_test.tftest.hcl      # Unit test (plan mode)    └── integration_test.tftest.hcl  # Integration test (apply mode - creates real resources)``` ## Refactoring Patterns ### Pattern 1: Resource GroupingExtract related resources into cohesive modules:- Networking (VPC, Subnets, Route Tables)- Compute (ASG, Launch Templates, Load Balancers)- Data (RDS, ElastiCache, S3) ### Pattern 2: Configuration Layering```hcl# Base module with defaultsmodule "vpc_base" {  source = "./modules/vpc-base"  # Minimal required inputs} # Environment-specific wrappermodule "vpc_prod" {  source = "./modules/vpc-production"  # Inherits from base, adds prod-specific config}``` ### Pattern 3: Composition```hcl# Small, focused modulesmodule "vpc" {  source = "./modules/vpc"} module "security_groups" {  source = "./modules/security-groups"  vpc_id = module.vpc.vpc_id} module "application" {  source     = "./modules/application"  vpc_id     = module.vpc.vpc_id  subnet_ids = module.vpc.private_subnet_ids  sg_ids     = module.security_groups.app_sg_ids}``` ## Common Pitfalls ### 1. Over-Abstraction```hcl# ❌ Don't create overly generic modulesvariable "resources" {  type = map(map(any))  # Too flexible, hard to validate} # ✅ Do use specific, typed interfacesvariable "database_config" {  type = object({    engine         = string    instance_class = string  })}``` ### 2. Tight Coupling```hcl# ❌ Don't couple modules through direct references# module Aoutput "instance_id" { value = aws_instance.app.id } # module B (in same config)resource "aws_eip" "app" {  instance = module.a.instance_id  # Tight coupling} # ✅ Do pass dependencies through root modulemodule "compute" {  source = "./modules/compute"} resource "aws_eip" "app" {  instance = module.compute.instance_id}``` ### 3. State Migration ErrorsAlways test migration in non-production first:```bash# Create plan to verify no changes after migrationterraform plan -out=migration.tfplan # Review carefullyterraform show migration.tfplan # Apply only if plan shows no changesterraform apply migration.tfplan``` ## Version Control Strategy ```hcl# Use semantic versioning for modulesmodule "vpc" {  source  = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"  version = "~> 1.2"} # Pin to specific versions in production# Use version ranges in development``` ## Success Criteria - [ ] Module has single, well-defined responsibility- [ ] All variables have descriptions and types- [ ] Validation rules prevent invalid configurations- [ ] Outputs provide sufficient information for consumers- [ ] Documentation includes usage examples- [ ] Tests verify module behavior- [ ] State migration completed without resource recreation- [ ] No plan differences after refactoring ## Related Skills- [Terraform code generation](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/terraform-style-guide/SKILL.md) - Style guide for the new Terraform Module- [Azure Verified Modules](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/azure-verified-modules/SKILL.md) - Recommended module specifications for Azure ## Resources- [Terraform Module Development](https://developer.hashicorp.com/terraform/language/modules/develop)- [Module Best Practices](https://developer.hashicorp.com/terraform/cloud-docs/registry/design) ## Revision History | Version | Date | Changes ||---------|------|---------|| 1.0.0 | 2025-11-07 | Initial skill definition |