mcpField
R
refactor-module

refactor-module

github.com/hashicorp云与 DevOps9096.4k 次安装今天更新
代码仓库

关于 refactor-module

refactor-module 是一项指导 AI Agent 将单体 Terraform 配置拆分为可复用模块的技能,涵盖模块接口设计、代码重构、文档、测试和状态迁移规划。

主要功能

  1. 01分析资源与依赖关系
  2. 02设计模块变量和输出
  3. 03将资源分组为模块
  4. 04规划状态迁移
  5. 05编写模块文档和测试
  6. 06检查迁移计划

适用场景

  1. 01将单体配置拆分为模块
  2. 02重构时保留资源状态
  3. 03为不同环境准备可复用模块

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hashicorp/agent-skills --skill refactor-module

安装后在 Claude Code 中输入/refactor-module确认已加载。

详细介绍

技能:重构模块

概述

本技能将指导 AI Agent 按照 HashiCorp 的模块设计原则和社区最佳实践,将单体 Terraform 配置转换为可复用、易维护的模块。

能力说明

Agent 将分析现有 Terraform 代码,并系统地将其重构为结构良好的模块,其中包含:

  • 清晰的接口契约(变量和输出)
  • 恰当的封装和抽象
  • 版本管理和文档
  • 测试框架
  • 现有状态的迁移路径

先决条件

  • 要重构的现有 Terraform 配置
  • 对资源依赖关系的了解
  • 可通过 terraform state list / terraform show -json 检查当前状态(用于规划迁移)
  • 对模块注册表模式的了解

输入参数

参数类型必填说明
source_directorystring是现有 Terraform 配置的路径
module_namestring是新模块的名称
abstraction_levelstring否"simple"、"intermediate"、"advanced"(默认值:intermediate)
preserve_stateboolean是是否保持状态兼容性
target_registrystring否目标模块注册表(local、private、public)

执行步骤

1. 分析阶段
**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. 模块设计
接口设计
# Define clear input contract
variable "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 contract
output "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 }
}
封装策略
**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. 代码转换
转换前:单体配置
# 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
转换后:模块化结构
# modules/vpc/main.tf
locals {
  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.tf
variable "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.tf
output "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 module
module "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. 状态迁移
检查当前状态

在编写 moved 块或 state mv 命令之前,请检查当前状态以映射现有资源地址。优先使用有文档说明、稳定且更节省 Token 的命令,而不是读取原始状态文件:

# Enumerate current resource addresses (the inputs for `moved` / `state mv`)
terraform state list

# Inspect resolved attribute values when you need them
terraform show -json | jq '.values.root_module'

terraform show -json 需要安装 provider(terraform init),因为它会根据 provider 架构呈现值。仅当 provider 不可用且无法运行 init、只需要粗略信息(地址、输出、serial/lineage),或者必须避免执行 Terraform 时,才回退到原始状态(terraform state pull / terraform.tfstate)。避免将原始的第 4 版状态格式解析为稳定接口。**注意:**所有格式的状态都以明文包含敏感值;切勿将状态内容输出到日志或其他输出中。

生成迁移计划
# 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]
}
手动迁移状态(Terraform 1.1 之前)
# Generate state migration commands
terraform state mv aws_vpc.main module.vpc.aws_vpc.main
terraform 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. 模块文档
# VPC Module

## Overview
Creates 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

\`\`\`hcl
module "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. 测试

使用技能 terraform-test

测试文件:包含测试配置和 run 块的 .tftest.hcl 或 .tftest.json 文件,用于验证 Terraform 配置。

Test 块:用于定义测试级设置的可选配置块(Terraform 1.6.0 起提供)。

Run 块:定义单个测试场景,可包含可选变量、provider 配置和断言。每个测试文件至少需要一个 run 块。

Assert 块:包含必须计算为 true 才能通过测试的条件。断言失败会导致测试失败。

Mock Provider:模拟 provider 行为,但不会创建真实基础设施(Terraform 1.7.0 起提供)。

测试模式:测试以 apply 模式(默认,会创建真实基础设施)或 plan 模式(验证逻辑但不创建资源)运行。

文件结构

Terraform 测试文件使用 .tftest.hcl 或 .tftest.json 扩展名,通常放在 tests/ 目录中。采用清晰的命名约定,以区分单元测试(plan 模式)和集成测试(apply 模式):

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)

重构模式

模式 1:资源分组

将相关资源提取到内聚的模块中:

  • 网络(VPC、子网、路由表)
  • 计算(ASG、启动模板、负载均衡器)
  • 数据(RDS、ElastiCache、S3)
模式 2:配置分层
# Base module with defaults
module "vpc_base" {
  source = "./modules/vpc-base"
  # Minimal required inputs
}

# Environment-specific wrapper
module "vpc_prod" {
  source = "./modules/vpc-production"
  # Inherits from base, adds prod-specific config
}
模式 3:组合
# Small, focused modules
module "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
}

常见陷阱

1. 过度抽象
# ❌ Don't create overly generic modules
variable "resources" {
  type = map(map(any))  # Too flexible, hard to validate
}

# ✅ Do use specific, typed interfaces
variable "database_config" {
  type = object({
    engine         = string
    instance_class = string
  })
}
2. 紧密耦合
# ❌ Don't couple modules through direct references
# module A
output "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 module
module "compute" {
  source = "./modules/compute"
}

resource "aws_eip" "app" {
  instance = module.compute.instance_id
}
3. 状态迁移错误

始终先在非生产环境中测试迁移:

# Create plan to verify no changes after migration
terraform plan -out=migration.tfplan

# Review carefully
terraform show migration.tfplan

# Apply only if plan shows no changes
terraform apply migration.tfplan

版本控制策略

# Use semantic versioning for modules
module "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

成功标准

  • 模块具有单一且定义明确的职责
  • 所有变量都有描述和类型
  • 验证规则可防止无效配置
  • 输出为使用者提供足够的信息
  • 文档包含使用示例
  • 测试可验证模块行为
  • 状态迁移已完成,且未重新创建资源
  • 重构后计划没有差异

相关技能

资源

修订历史

版本日期变更
1.0.02025-11-07初始技能定义

评论

支持 Markdown