Claude Media
Claude CodeでTerraformのimportを進める — 生成HCLの整形とplan差分0

Claude CodeでTerraformのimportを進める — 生成HCLの整形とplan差分0

手作業で作った既存リソースをimportブロックで取り込むとき、generate-config-outの出力をClaude Codeに整えさせ、planは許可・applyとimportコマンドは拒否で区切る手順です。

コンソールから手で作ったリソースをTerraformの管理下に入れるとき、Claude Codeに任せられるのは「HCLを整えて、planの数字を報告する」までです。取り込みを確定するapplyは人が実行します。この分担を、importブロックとterraform plan -generate-config-outの流れに沿って権限設定で区切るのが、この記事の手順です。新しくコードを書く側の検証ループはTerraformのコードを書く手順にあり、ここでは既存リソースの取り込みだけを扱います。

importブロックは何を取り込み、何を取り込まないか

HashiCorpの説明では、importには2つの部品が要ります。取り込む実リソースのIDと、stateでのアドレスを宣言するimportブロック。そのアドレスと一致する、設定側のresourceブロックです。

import {
  to = aws_s3_bucket.assets
  id = "acme-prod-assets"
}

toは取り込み先のアドレス、idはクラウド側のIDです。IDの書き方はリソースの種類ごとに違い、プロバイダーのドキュメントに従います。idの代わりに、複数の属性でリソースを特定するidentityも使えますが、同じブロックに両方は書けません。

手でresourceブロックを書く代わりに、importブロックだけを書いてterraform plan -generate-config-out=<パス>を実行すると、Terraformがresourceブロックの下書きを新しいファイルに書き出します。HashiCorpはこの出力を「Terraformによる最善の推測を入れたテンプレート」と位置づけ、出発点として属性を削ったり値を直したり、ファイルやモジュールに並べ替えたりして仕上げるよう勧めています。生成機能は実験的な扱いで、将来のバージョンで出力の書式が変わる可能性があるとされています。

つまり、生成されたHCLは完成品ではありません。ここがClaude Codeの出番です。

役割を分ける

取り込みの工程を、誰が何をするかで分けます。

6段

取り込みの分担

  1. 1

    対象とIDを決める: 人

    どのリソースをどのアドレスに入れるかを決め、IDをプロバイダーのドキュメントで確かめます。

  2. 2

    importブロックを書く: Claude

    決まった対象とIDをimports.tfに書きます。

  3. 3

    下書きを生成する: Claude(plan)

    terraform plan -generate-config-outで新しいファイルに出力させます。

  4. 4

    HCLを整える: Claude

    ハードコード値の変数化、不要な属性の整理、ファイルへの振り分けをします。

  5. 5

    planで差分を確かめる: Claude が報告、人が読む

    追加・変更・削除が0であることを数字で報告させます。

  6. 6

    取り込みを確定する: 人(apply)

    terraform applyを実行するのは人だけです。

importブロックを使うと、取り込みも通常のplanとapplyの流れに乗ります。HashiCorpの例では、planは「1 to import, 0 to add, 0 to change, 0 to destroy」のように取り込み予定を表示し、実際の取り込みはapplyで行われます。planまでが読み取りの工程で、stateを変えるのはapplyだけ、という切れ目がそのまま権限の境界になります。

権限を取り込み用に組む

planの許可とapplyの拒否の基本形は、Terraformのコードを書く手順にあります。取り込みでは、それに加えて古いterraform importコマンドを止める必要があります。

{
  "permissions": {
    "allow": [
      "Bash(terraform fmt *)",
      "Bash(terraform validate *)",
      "Bash(terraform init)",
      "Bash(terraform plan *)"
    ],
    "deny": [
      "Bash(terraform apply *)",
      "Bash(terraform import *)",
      "Bash(terraform state *)",
      "Bash(terraform destroy *)"
    ]
  }
}

terraform importをdenyに入れるのが、この記事の要点です。このコマンドはterraform import ADDRESS IDの形で、リソースをstateに取り込みます。手動のコマンドよりimportブロックが推奨されており、ブロックならapplyの流れで取り込みを確認できます。Claudeにコマンドを打たせると、planを経由しない取り込みの道が開きます。

同じコマンドで同じ実リソースを複数のアドレスに取り込むと、予期しない挙動が出る可能性があるとも、HashiCorpは警告しています。アドレスと実リソースを1対1に保つ確認は、取り込み前の人の仕事です。

ルールの評価は、denyが最初、次にask、最後にallowで、最初に一致したものが結果を決めます。Bash(terraform apply *)は引数なしのterraform applyにも一致します。*をサブコマンドの後ろに置く書き方の理由と、terraform -chdir=infra applyのようなオプション先頭の呼び出しがすり抜ける点は、Terraformのコードを書く手順で扱っています。複数ユニットを束ねる構成ならTerragruntのrun --allも同じ考え方です。OpenTofuを使う場合の書き方はOpenTofuの権限設計にあります。

terraform initは引数なしの完全一致で許可しています。-backend=falseを付けた検証用の初期化と違い、取り込みのplanは実際のbackendとプロバイダーに接続して現状を読むためです。接続に使う認証情報は、読み取り権限だけのものに絞っておくと、planを許可しても実リソースへの書き込み経路ができません。この点はTerraformでなくクラウド側の権限設計の話です。

CLAUDE.mdに取り込み用の手順を書く

権限設定が「してはいけないこと」を強制するのに対し、CLAUDE.mdは「してほしい順番」を伝えます。取り込み専用の節を分けておきます。

# 既存リソースの取り込み
 
1. 取り込み対象は依頼文のアドレスとIDだけ。`imports.tf` に `import` ブロックを書く
2. 下書きは `terraform plan -generate-config-out=gen_<連番>.tf` で作る。
   同名のファイルが既にあるとエラーになるので、毎回連番を変える
3. 生成された `resource` ブロックは `gen_*.tf` に残さず、`<種類>.tf` に移して
   元の `gen_*.tf` は削除を人に依頼する
4. 次の値は直書きにしない: アカウントID・リージョン・環境名・ARN
   変数または data ソースで受け、既定値は現状の実値と同じにする
5. 整えるたびに `terraform fmt` と `terraform validate`
6. 最後に `terraform plan` を実行し、import / add / change / destroy の
   件数を報告する。add・change・destroy が 0 でなければ apply を勧めない
7. `terraform apply` と `terraform import` は実行しない

2番の「同名のファイルがあるとエラー」は、HashiCorpの記述に基づきます。-generate-config-outには新しいパスを渡す必要があり、既存のファイルを指定するとエラーになります。Claudeが同じコマンドをやり直すときに、前回の出力ファイルを上書きできないので、連番を変える運用にしています。

3番で削除を人に頼むのは、rmをallowに入れないためです。

生成されたHCLを整える

生成直後のファイルは、実リソースの現在の値がそのまま入った状態です。HashiCorpの例を見ると、planの画面にはarn・id・versionのような属性が並ぶ一方、書き出されたファイルにはnameだけが入っています。画面の表示とファイルの内容は一致しないので、ファイルを読んで整えます。

整形の対象は、次の3種類に分けると指示が出しやすくなります。

整える対象例Claudeへの指示の要点
環境ごとに変わる値例アカウントID・リージョン・環境名Claudeへの指示の要点変数にして、既定値は現状の値に合わせる
他のリソースから引ける値例同じ構成内のIDやARNClaudeへの指示の要点参照式に置き換える
競合する引数例排他の属性が両方入るClaudeへの指示の要点片方を削る

3番目は、HashiCorpが制限として挙げています。複雑なスキーマのリソースでは、プロバイダーから返る値だけで有効な設定を組み立てられないことがあり、ipv6_address_countとipv6_addressesのような排他の引数が両方入ってエラーになる例が示されています。この場合も設定は書き出されるので、片方を削ってplanをやり直します。

変数化の指示は、次のように「値を変えない」ことを先に伝えます。以下はプロンプトの例です。

gen_1.tf の aws_s3_bucket.assets を storage.tf に移して整えて。
- バケット名のアカウントID部分は変数 account_id にする。既定値は今の値のまま
- 値を変える提案はしない。見た目の整理と変数化だけ
- 終わったら terraform fmt / validate / plan を順に実行して、
  import / add / change / destroy の件数を報告して

変数にして既定値を現状の値に合わせておくと、HCLが変わっても評価結果は変わらず、planの差分は増えません。値を変えてしまうと、属性によっては変更や作り直しとしてplanに現れます。整形のたびにplanを通す理由はここにあります。

落とし穴

生成ファイルを別名に移し忘れる

gen_1.tfに生成されたブロックを、storage.tfへ写したあとも残すと、同じアドレスのresourceが二重になり、validateがDuplicate resourceで止まります。Claudeは削除を実行できない設定なので、写し終えたら人に削除を頼むところまでを手順に含めておきます。

plan差分0をどう確かめるか

整え終えたら、Claudeにterraform planを実行させ、次の件数を報告させます。

terraform plan

取り込みだけが予定されている状態の目安は、HashiCorpの例と同じ形です。

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

読むのはadd・change・destroyの3つです。importの数は、依頼したアドレスの数と一致していれば足ります。この3つが0でないなら、HCLの値が実リソースとずれています。ずれた属性をplanの差分から特定して、HCLを実値に合わせ直します。ずれたままapplyすると、取り込みと同時に実リソースが書き換わります。

確定後の確認は、Claudeにもう一度planを実行させ、差分がないことを見ます。-detailed-exitcodeを付けると、終了コード0が差分なし、2が差分ありになるので、「0以外なら報告して止まる」という指示が書けます。

terraform plan -detailed-exitcode

取り込み後に差分が出続けるときは、HCLを書き直して直るずれか、実リソースの側が変わったずれかを、planの差分の属性から見分けます。

取り込み後に回すチェック

取り込んだHCLは、新規に書いたコードと同じく静的検査にかけられます。セキュリティの設定ミスを拾うなら、Checkovでのスキャンが続きになります。コマンドを許可に入れる範囲の考え方は、/permissionsコマンドの解説も参考になります。

生成機能は実験的と明記されているため、terraformのバージョンを上げた後に同じ手順を回すと、出力の書式が変わっていることがあります。CLAUDE.mdの手順は「生成された書式を前提にしない」ように、値と構造を読んで整える書き方にしておきます。

取り込みの確定は人が持つ

この手順で、Claudeに渡るのは「生成」「整形」「検証」の3工程です。取り込みの確定はapplyにだけ置き、terraform importとstateを拒否することで、stateを書き換える道を塞いでいます。HCLが速く整うぶん、確定の前に人がplanの3つの数字を読む工程だけは、省かずに残します。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developer.hashicorp.com/terraform/language/import
  2. developer.hashicorp.com/terraform/language/import/generating-configuration
  3. developer.hashicorp.com/terraform/language/block/import
  4. developer.hashicorp.com/terraform/cli/commands/plan
  5. developer.hashicorp.com/terraform/cli/commands/import
  6. code.claude.com/docs/en/permissions
この記事を共有:XはてブLinkedIn