refactor-module について
refactor-module は、AI エージェントがモノリシックな Terraform 設定を再利用可能なモジュールに変える作業を支援するスキルです。モジュールのインターフェース、コードの再構成、ドキュメント、テスト、状態移行の計画を扱います。
主な機能
- 01リソースと依存関係の分析
- 02モジュール変数と出力の設計
- 03リソースのモジュール化
- 04状態移行の計画
- 05モジュールの文書化とテスト
- 06移行計画の確認
活用シーン
- 01モノリシックな設定のモジュール分割
- 02リファクタリング時のリソース状態の維持
- 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_directory | string | はい | 既存の Terraform 構成へのパス |
module_name | string | はい | 新しいモジュールの名前 |
abstraction_level | string | いいえ | "simple"、"intermediate"、"advanced"(デフォルト:intermediate) |
preserve_state | boolean | はい | 状態との互換性を維持するかどうか |
target_registry | string | いいえ | 対象のモジュールレジストリ(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
成功基準
- モジュールの責務が単一かつ明確に定義されている
- すべての変数に説明と型がある
- 検証ルールによって無効な構成が防止されている
- 出力に利用者が必要とする十分な情報が含まれている
- ドキュメントに使用例が含まれている
- テストによってモジュールの動作が検証されている
- リソースを再作成せずに状態の移行が完了している
- リファクタリング後にプランの差分がない
関連スキル
- Terraform コード生成 - 新しい Terraform モジュールのスタイルガイド
- Azure Verified Modules - Azure 向けの推奨モジュール仕様
リソース
改訂履歴
| バージョン | 日付 | 変更内容 |
|---|---|---|
| 1.0.0 | 2025-11-07 | 初版のスキル定義 |