Communityコーディング&開発github.com

daily-dreamer/terraform-standardize

生のTerraformコード、ブラウンフィールド、Terraformerインポートコード(AWS、GCP、Azure)をHashiCorp標準の本番環境向けアーキテクチャに標準化します。provider.tf、versions.tf、backend.tfを分離し、ハードコードされた文字列を型付きのvariables.tfとlocals.tfにパラメータ化し、静的なクラウドIDを動的参照に置き換え、movedブロックによるForceNewやゼロドリフトのガードレールを適用し、標準的なHCLフォーマット、バリデーション、プラン差分サマリーを実行します。

terraform-standardize とは?

このスキルは、生のTerraformコード、ブラウンフィールド、Terraformerや`terraform import`でインポートされたコード、レガシーなTerraformコードを、モジュール化され本番環境に対応したHashiCorp標準アーキテクチャにリファクタリングするための詳細なガイドラインを提供します。AWS、GCP、Azureの3大クラウドプロバイダーに対応し、ハードコードされたリテラルや静的IDの検出、標準ファイル構成(provider.tf、versions.tf、variables.tf、locals.tf、outputs.tf、backend.tf)への分割、変数とローカルへのパラメータ化、動的参照への置き換え、そして`terraform fmt`と`terraform validate`による検証までを5つのフェーズでカバーします。特に、Terraformerでインポートしたコードのクリーンアップ、movedブロックを使用したゼロドリフトなリファクタリング、ForceNew属性の誤った変更によるリソース再作成の防止、そしてplan差分のサマリー作成に重点を置いており、AIエージェントが安全かつ確実にTerraformコードベースを標準化できるよう設計されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/daily-dreamer/terraform-standardize/tree/HEAD/skills/terraform-standardize

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Terraform 標準化スキル

このスキルは、エージェントが生の、ブラウンフィールド、インポート済み(例:Terraformer や terraform import から)、またはレガシーな Terraform コードを、モジュール化された本番環境対応の HashiCorp 標準アーキテクチャにリファクタリングするためのガイドを提供します。

このスキルを使用するタイミング

以下の場合にこのスキルを有効にします:

  • ユーザーが Terraform 設定の「標準化」、「リファクタリング」、「クリーンアップ」、「モジュール化」を依頼した場合。
  • 生の Terraform コードにハードコードされた文字列、マジックナンバー、静的なクラウド ID(ami-*、vpc-*、subnet-*、sg-*、arn:*)が含まれている場合。
  • モノリシックな単一ファイル構成(main.tf にすべてが含まれている)を、標準的な複数ファイルレイアウト(provider.tf、variables.tf、locals.tf、outputs.tf、backend.tf)に分割する必要がある場合。
  • クラウドリソースに標準化されたタグ付けルール(例:Environment、Project、ManagedBy = "terraform")が必要な場合。
  • コードベースに変数定義、型制約、説明ブロックが不足している場合。

標準化ワークフロー

Terraform コードベースを標準化する際は、以下の5フェーズの手順に従います:

┌────────────────────────────────────────────────────────┐
│ フェーズ1: 監査とコードスメルの特定                         │
│ • ハードコードされたリテラル、ID、リージョン、CIDR を検出    │
│ • 既存のファイル構造と標準レイアウトを比較                     │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ フェーズ2: 標準ファイルアーキテクチャの確立                   │
│ • provider.tf、versions.tf、backend.tf を抽出             │
│ • variables.tf、locals.tf、outputs.tf を作成              │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ フェーズ3: リテラルを変数とローカルにパラメータ化               │
│ • 環境/リージョン変数を型付きで抽出                           │
│ • 統一リソースタグ付けのための local.common_tags を構築       │
│ • terraform.tfvars.example を作成                       │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ フェーズ4: 動的参照リンクとリネーム                           │
│ • 静的なリソースIDを動的参照に置き換え                         │
│ • ハードコードされたAMI/サブネットをデータソースに置き換え       │
│ • リソースラベルを標準化(スネークケース、タイプなし)           │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ フェーズ5: フォーマット、検証、確認                           │
│ • `terraform fmt -recursive` を実行                      │
│ • `terraform validate` を実行                             │
│ • `terraform plan` で確認(ゼロドリフト)                  │
└────────────────────────────────────────────────────────┘

ステップバイステップの手順

1. フェーズ1: 監査と発見

編集を開始する前に、ターゲットディレクトリをスキャンして以下を特定します:

  1. CLI の可用性: terraform version(または OpenTofu の場合は tofu version)を実行して、フェーズ5で CLI のフォーマット、検証、プランチェックを実行できるかどうかを確認します。
  2. 欠落している標準ファイル: versions.tf、provider.tf、variables.tf、locals.tf、outputs.tf が存在するか確認します。
  3. ハードコードされた ID とリテラル:
    • クラウド識別子: ami-[a-f0-9]+、vpc-[a-f0-9]+、subnet-[a-f0-9]+、sg-[a-f0-9]+、igw-[a-f0-9]+、完全な GCP セルフリンク(https://www.googleapis.com/compute/...)、静的な project ID
    • CIDR ブロック: "10.0.0.0/16"、"0.0.0.0/0"
    • インスタンスタイプ: "t3.micro"、"m5.large"、"e2-standard-4"
    • 静的なリージョンとアカウント番号: "us-east-1"、"us-central1"、"123456789012"
    • 生の認証情報や暗号化されていない機密情報
  4. フォーマットのベースライン: Terraform がインストールされている場合、ファイルが terraform fmt -check -recursive に準拠しているか確認します。

2. フェーズ2: 標準コアファイルの作成

ターゲット設定が、明確で単一責任のファイルに分割されていることを確認します:

provider.tf / versions.tf

Terraform 設定、最小 CLI バージョン、必要なプロバイダーを一元管理します(AWS、GCP、Azure に合わせて調整):

# versions.tf	erraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    # GCP の例:
    # google = {
    #   source  = "hashicorp/google"
    #   version = "~> 5.5"
    # }
  }
}

# provider.tf (AWS)
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

# provider.tf (GCP の代替)
# provider "google" {
#   project        = var.project_id
#   region         = var.region
#   default_labels = local.common_labels
# }

単一タグ付け/ラベル付け戦略: プロバイダーレベルの default_tags / default_labels を使用するか(各リソースにはリソース固有のキーのみを設定)、または各リソースで merge(local.common_tags, ...) / merge(local.common_labels, ...) を使用します。両方を重複してはいけません。

backend.tf(リモートステートを使用する場合)

ステートストレージ設定を分離します(AWS の場合は s3、GCP の場合は gcs、Azure の場合は azurerm):

  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "services/app/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

# GCP の例:
# terraform {
#   backend "gcs" {
#     bucket = "myorg-terraform-state"
#     prefix = "services/app"
#   }
# }

3. フェーズ3: プレーン文字列を変数とローカルに変換

すべてのハードコードされたパラメータを variables.tf と locals.tf に抽出します:

variables.tf のルール:

  • 常に type を宣言: (string、number、bool、list(string)、map(string))。
  • 常に description を含める: 目的と期待される形式を説明します。
  • 適切な場合にのみ default を提供: environment や aws_region のような設定オプションにはデフォルト値を設定しても構いませんが、機密値にはハードコードされたデフォルト値を設定してはいけません。
  • シークレットを sensitive = true とマーク: パスワード、トークン、シークレット変数は機密としてマークする必要があります。

variables.tf の例:

variable "aws_region" {
  type        = string
  description = "The AWS region where resources will be provisioned."
  default     = "us-east-1"
}

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

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

variable "instance_type" {
  type        = string
  description = "EC2 instance size for the application cluster."
  default     = "t3.medium"
}

variable "vpc_cidr" {
  type        = string
  description = "Base CIDR block for the Virtual Private Cloud."
  default     = "10.0.0.0/16"
}

locals.tf のルール:

内部で派生した値、環境プレフィックス、共通タグには locals を使用します:

locals {
  name_prefix = "${var.project_name}-${var.environment}"

  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

terraform.tfvars.example

同僚が値を指定する方法を理解できるように、常に変数値のサンプルファイルを作成します:

aws_region   = "us-east-1"
environment  = "production"
project_name = "checkout-service"

4. フェーズ4: 静的な値を動的参照に置き換える(ゼロドリフトガードレール)

静的なクラウド ID と分離されたリソース参照を、ライブインフラストラクチャの誤った置き換えから保護しながら、ネイティブな HCL 式に変換します:

不変性と ForceNew ガードレール(ブラウンフィールド / Terraformer では重要)

  • Terraform アドレスとクラウドの name 属性を区別する: moved {} ブロックを使用して Terraform リソースラベル(tfer--prod-main-vpc → main)をリネームしても、ダウンタイムは発生せず安全です。クラウドの name 属性(例:google_compute_network.name、google_compute_subnetwork.name、google_compute_firewall.name、google_compute_instance.name、aws_s3_bucket.bucket)を変更することは ForceNew であり、ライブインフラストラクチャを破壊して再作成します。既存のクラウド名を "${local.name_prefix}-..." のように書き換えるのではなく、variables.tf / terraform.tfvars でパラメータ化します。
  • ブラウンフィールドインポート時にインポートされたイメージ/AMI を固定する: ハードコードされた ami-* や debian-12-bookworm-v20240515 を most_recent = true / family data ルックアップに切り替えると、新しいイメージが存在する場合に既存の VM が置き換えられます。ゼロドリフトが必要な場合は、変数/data フィルターを介して正確なインポートされたイメージ/AMI 名または ID を固定し、ユーザーがイメージのアップグレードを選択した場合にのみ most_recent = true を有効にします。
  • セキュリティ強化をゼロドリフトリファクタリングから分離する: ゼロドリフト移行中に、動作上/セキュリティ上の属性(例:http_tokens の "optional" → "required"、public_access_prevention の "inherited" → "enforced"、VM OAuth scopes)を黙って変更しないでください。強化は明示的なオプトインとして扱うか、すべての意図的な差分を明確にリストアップします。
  • 移行時にレガシータグ/ラベルキーを保持する: 既存のリソースが env = "prod" を使用している場合、キーの置き換えが承認されない限り、既存のキーを保持するか(標準化されたキーをそれらと一緒に追加します)。

Terraformer 固有のクリーンアップチェックリスト

  • 計算された読み取り専用のステート属性(AWS の tags_all、GCP の effective_labels と terraform_labels)と、空/デフォルトの属性(""、[]、{}、false、0)を削除します。
  • 競合するステートダンプされたサブブロック(例:subnet_id が設定されている場合の EC2 プライマリ network_interface)を削除します。
  • レガシーな ${data.terraform_remote_state.*.outputs.*} 補間と完全なセルフリンク URL を、直接的なリソース参照(例:google_compute_network.main.id)または data ルックアップに置き換えます。
  • 番号が付けられた重複リソース(vm-01、vm-02)を、moved ブロック(to = google_compute_instance.api["01"])と組み合わせた単一の for_each リソースにまとめます。
  • インラインの \n エスケープされた user_data / startup-script 文字列を、file() または templatefile() で読み込まれる scripts/ ファイルに抽出します。
  • GCP VM の scopes または machine_type を変更する場合、インプレース更新を意図している場合は allow_stopping_for_update = true が設定されていることを確認します。

アンチパターン(生 / ハードコード):

# BAD: ハードコードされた ID とリテラル文字列
resource "aws_instance" "tfer--i-0123456789abcdef0" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"
  subnet_id     = "subnet-01234567"
  vpc_security_group_ids = [
    "sg-0abcdef1234567890"
  ]
}

標準パターン(リファクタリング後):

# 1. ステートの保存: このリソースの名前が変更されたことを Terraform に伝えます(破棄と再作成を防ぎます)
moved {
  from = aws_instance.tfer--i-0123456789abcdef0
  to   = aws_instance.web
}

# 2. ベースイメージの動的ルックアップ(ゼロドリフトブラウンフィールドの場合は正確な名前を固定、グリーンフィールドの場合は most_recent)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = [var.ami_name_pattern]
  }
}

# 3. 動的参照を使用した標準化されたリソース定義
resource "aws_instance" "web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.private[0].id
  vpc_security_group_ids = [aws_security_group.web.id]

  # provider.tf ですでに default_tags が設定されている場合は、ここで common_tags を省略します
  tags = {
    Name = var.instance_name
  }
}

5. フェーズ5: フォーマット、ドリフト監査、および検証

  1. プラン前ドリフト監査チェックリスト(常に実行します。特に terraform CLI がローカルにインストールされていない場合):

    • すべてのリソースのクラウド name、image/ami、location/zone、tags/labels、セキュリティ/ネットワーク設定を、インポート元と比較します。
    • ForceNew フィールドが誤って変更されていないこと、およびすべての tfer--* リソースに対応する moved {} ブロックがあることを確認します。
    • 意図的な差分(例:セキュリティ強化)とゼロドリフトリファクタリングを分離して報告します。
  2. Terraform CLI チェックを実行(インストールされている場合):

# 1. コードを標準のカノニカルな HCL インデントにフォーマット
terraform fmt -recursive

# 2. プロバイダープラグインを初期化し(オフライン/ステートなしで検証する場合は -backend=false を使用)、構文を検証
terraform init -backend=false
terraform validate

# 3. アクティブな環境でステートを使用して作業している場合は、plan で意図しない差分が発生しないことを確認
terraform plan
  1. terraform plan の変更を要約(差分が検出された場合): terraform plan が No changes 以外を報告した場合は、apply を推奨する前に停止し、構造化されたサマリーを提示します:
    • Plan の集計: 正確なリソース数(X to add, Y to change, Z to destroy)と、moved ステート遷移があればそれを記載します。
    • リスクの高い変更を最初に(-/+ 置換、- 削除): 置換または削除対象としてマークされたすべてのリソースを強調表示し、それを引き起こしている ForceNew 属性(例:name、image、boot_disk)を指名し、修正方法(欠落している moved {} ブロックの追加や、元の値を tfvars に復元するなど)を説明します。
    • インプレース更新(~)と作成(+): 影響を受ける各リソースと、変更される正確な属性(例:labels、tags、scopes、public_access_prevention)をリストします。
    • 意図的なドリフトと意図しないドリフト: どの差分が意図的(オプトインされたセキュリティ強化やタグ標準化)であり、どの差分が意図しないブラウンフィールドドリフトであるかを明確にマークし、ユーザーが承認または差し戻しできるようにします。

詳細なガイドラインと参考資料

関連スキル