mcpField
R
refactor-module

refactor-module

github.com/hashicorpクラウド・DevOps9096.4k 回インストール今日更新
リポジトリ

refactor-module について

refactor-module は、AI エージェントがモノリシックな 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を入力して読み込まれたか確認します。

詳細

スキル:モジュールのリファクタリング

概要

このスキルでは、HashiCorp のモジュール設計原則とコミュニティのベストプラクティスに従って、モノリシックな Terraform 構成を再利用可能で保守しやすいモジュールに変換する方法を AI エージェントに示します。

機能の説明

エージェントは既存の 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 コマンドを記述する前に、現在の状態を調査し、 既存のリソースアドレスを対応付けます。生の状態ファイルを読み取るよりも、 ドキュメント化された、安定性が高く、トークン効率に優れたコマンドを優先します。

# 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 では、プロバイダーのスキーマに基づいて値を表示するため、プロバイダーがインストールされている必要があります(terraform init)。プロバイダーが利用できず、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]
}
手動での状態移行(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 を使用します

テストファイル:Terraform 構成を検証するテスト構成と run ブロックを含む .tftest.hcl または .tftest.json ファイルです。

Test ブロック:テスト全体に適用される設定を定義する任意の構成ブロックです(Terraform 1.6.0 以降で利用可能)。

Run ブロック:変数、プロバイダー構成、アサーションを任意で指定し、単一のテストシナリオを定義します。各テストファイルには、少なくとも 1 つの run ブロックが必要です。

Assert ブロック:テストの合格に必要な条件を含みます。アサーションに失敗すると、テストも失敗します。

モックプロバイダー:実際のインフラストラクチャを作成せずに、プロバイダーの動作をシミュレートします(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初版のスキル定義

コメント