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の出番です。
役割を分ける
取り込みの工程を、誰が何をするかで分けます。
取り込みの分担
- 1
対象とIDを決める: 人
どのリソースをどのアドレスに入れるかを決め、IDをプロバイダーのドキュメントで確かめます。
- 2
importブロックを書く: Claude
決まった対象とIDを
imports.tfに書きます。 - 3
下書きを生成する: Claude(plan)
terraform plan -generate-config-outで新しいファイルに出力させます。 - 4
HCLを整える: Claude
ハードコード値の変数化、不要な属性の整理、ファイルへの振り分けをします。
- 5
planで差分を確かめる: Claude が報告、人が読む
追加・変更・削除が0であることを数字で報告させます。
- 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やARN | Claudeへの指示の要点参照式に置き換える |
| 競合する引数 | 例排他の属性が両方入る | 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つの数字を読む工程だけは、省かずに残します。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- developer.hashicorp.com/terraform/language/import
- developer.hashicorp.com/terraform/language/import/generating-configuration
- developer.hashicorp.com/terraform/language/block/import
- developer.hashicorp.com/terraform/cli/commands/plan
- developer.hashicorp.com/terraform/cli/commands/import
- code.claude.com/docs/en/permissions