planを読んで自動で判定する
目標
プランを、ファイルとJSONの2つの形で扱い、動作・終了コード・ドリフトを根拠に、「このプランは人が見る必要がある」をスクリプトが判定するようにします。
なぜ重要なのか
インフラコードで最も高くつくミスは、プランを読み間違えることです。置き換え(-/+)を、その場での更新(~)と勘違いすると、リソースが削除されて、また作られ、その間のデータは戻ってきません。ところが、プランの出力は長く、デプロイはたいてい夜に行われるので、人の目だけに任せる方式は、いつか必ず失敗します。そのため、2つの仕組みが必要です。1つ目は、プランをファイルに保存して、レビューしたそのプランをそのまま適用することです。保存しないと、レビューしたものと適用されるものが違う可能性があります。2つ目は、プランを機械が読む形式で取り出して、ルールで判定することです。-detailed-exitcodeの0/1/2と、JSONのresource_changes、resource_driftが、その材料です。このラボが終わると、「削除が1つでもあれば人が承認」のようなルールを、パイプラインに入れる材料が手元に残ります。
ステップ
/root/tf/planに、local_file1つとterraform_data1つを含めて、リソースを2つ以上宣言して、初期化してください。プランを/root/tf/plan/plan.tfplanファイルに保存したあと、そのプランファイルをJSONに変換して、/root/tf/plan/out/plan.jsonに保存します。.resource_changesに項目が2つ以上ある必要があります。plan.jsonの.resource_changes[].change.actionsを数えて、/root/tf/plan/out/actions.jsonを作成してください。create、update、deleteの3つのキーを持つJSONで、数字は、プランで数えた値とまったく同じでなければなりません。- まだ適用する前に、
-detailed-exitcodeを付けてプランを実行し、その終了コードだけを/root/tf/plan/out/exitcode-before.txtに書いてください。そのあと、適用して、同じコマンドをもう一度実行し、終了コードを/root/tf/plan/out/exitcode-after.txtに書きます。2つのファイルには、数字が1つずつだけ入ります。 local_fileの引数を変えて、削除後の再作成が出るようにし、同時に、terraform_dataのinputだけを変えて、その場での更新が出るようにしてください。2つの変更が1つのプランの中に一緒に入るようにプランを取り出して、JSONで/root/tf/plan/out/replace.jsonに保存します。- 適用して状態を合わせたあと、管理中の
local_fileの内容を、Terraformを経由せず、シェルで直接直してください。その状態でプランを取り出して、JSONで/root/tf/plan/out/drift.jsonに保存します。.resource_driftに、そのlocal_fileのアドレスが捕捉されている必要があります。 - 変更が2か所以上で待機している状態を作ったあと、
-targetでちょうど1つだけを狙ったプランを、JSONで/root/tf/plan/out/target.jsonに保存してください(no-opではない変更がちょうど1個)。同じ実行でツールが出す警告の文言を、/root/tf/plan/out/target-warning.txtに残します。 /opt/lab/fixtures/terraform/broken/main.tfを/root/tf/plan/broken/main.tfにコピーして、そのまま実行し、エラーの出力を/root/tf/plan/out/broken.txtに保存してください(引数名contentsが問題だというメッセージが含まれている必要があります)。そのあと、同じファイルを/root/tf/plan/fixed/main.tfにコピーして、contentsを正しい引数contentに直し、直した設定のプランを、JSONで/root/tf/plan/out/fixed.jsonに保存します。- 最後に、
/root/tf/planの設定から、リソースを1つ丸ごと削除して、削除が含まれるプランを作り、JSONで/root/tf/plan/out/final.jsonに保存してください。そのJSONを読んで、/root/tf/plan/out/review.jsonを作成します。add(動作にcreateが含まれる変更の数)、change(動作が["update"]の変更の数)、destroy(動作にdeleteが含まれる変更の数)の3つの数字と、削除対象のアドレスをすべて含むdestructive_addresses配列、そしてverdictをneeds-reviewとして入れてください。
参考
- プランファイルは、人が読む形式ではありません。
terraform show -json plan.tfplanで変換して、jqで扱ってください。 -detailed-exitcodeの終了コードは、0(変更なし)、1(エラー)、2(変更あり)です。コマンドの直後に$?を読む必要があり、set -eがかかったスクリプトでは、先に止まらないように処理する必要があります。terraform_dataは、プロバイダーなしでコアに組み込まれたリソースなので、このオフラインの環境でも使えます。inputだけを変えればその場での更新で、triggers_replaceを変えれば再作成です。逆に、local_fileは、どの引数を変えても再作成です。- ステップ7の
broken/とfixed/は、それぞれ独立した作業ディレクトリです。ディレクトリごとに別々に初期化しないとプランが出ず、エラーは標準エラーに出るので、2>&1で一緒にファイルに入れてください。 - よくある間違い1:
-targetを普段のワークフローとして使うこと。ツールが警告を出す理由は、グラフの一部だけを反映して、状態が食い違ったまま残るおそれがあるからです。 - よくある間違い2: 要約行で、置き換えを1件として数えること。置き換えは、addとdestroyの両方に1ずつ数えられるので、ステップ8の数字も、その基準で数えないと合いません。
プランをファイルに保存して、JSONに変換する
/root/tf/planに、local_file1つとterraform_data1つを含めて、リソースを2つ以上宣言して、初期化してください。プランを/root/tf/plan/plan.tfplanファイルに保存したあと、そのプランファイルをJSONに変換して、/root/tf/plan/out/plan.jsonに保存します。.resource_changesに項目が2つ以上ある必要があります。
プランをファイルに残すオプションがあり、そのファイルは、人が読む形式ではありません。JSONに変換するサブコマンドを探して、標準出力をファイルに渡してください。
動作ごとの個数を数えて記録する
plan.jsonの.resource_changes[].change.actionsを数えて、/root/tf/plan/out/actions.jsonを作成してください。create、update、deleteの3つのキーを持つJSONで、数字は、プランで数えた値とまったく同じでなければなりません。
各変更の動作は、配列として入っています。jqで、その配列に特定の値が入っている項目だけを選んで数えればよいです。3つのキーがすべてある必要があります。
-detailed-exitcodeの2つの値を確認する
まだ適用する前に、-detailed-exitcodeを付けてプランを実行し、その終了コードだけを/root/tf/plan/out/exitcode-before.txtに書いてください。そのあと、適用して、同じコマンドをもう一度実行し、終了コードを/root/tf/plan/out/exitcode-after.txtに書きます。2つのファイルには、数字が1つずつだけ入ります。
終了コードは、コマンドが終わった直後にしか読めません。途中で別のコマンドが挟まると値が変わり、シェルのオプションによっては、スクリプトが先に止まることもあります。
その場での更新と置き換えを、1つのプランに入れる
local_fileの引数を変えて、削除後の再作成が出るようにし、同時に、terraform_dataのinputだけを変えて、その場での更新が出るようにしてください。2つの変更が1つのプランの中に一緒に入るようにプランを取り出して、JSONで/root/tf/plan/out/replace.jsonに保存します。
local_fileは、どの引数を変えても置き換えです。その場での更新には、別の種類のリソースが必要で、プロバイダーなしでコアに組み込まれたものが1つあります。
コードの外の変更を、ドリフトとして捕捉する
適用して状態を合わせたあと、管理中のlocal_fileの内容を、Terraformを経由せず、シェルで直接直してください。その状態でプランを取り出して、JSONで/root/tf/plan/out/drift.jsonに保存します。.resource_driftに、そのlocal_fileのアドレスが捕捉されている必要があります。
適用して状態を合わせたあと、成果物だけをシェルで直してください。プランのJSONには、計画された変更と、照会で見つけた変更が、別のフィールドに入ります。
対象を絞って、警告を読む
変更が2か所以上で待機している状態を作ったあと、-targetでちょうど1つだけを狙ったプランを、JSONで/root/tf/plan/out/target.jsonに保存してください(no-opではない変更がちょうど1個)。同じ実行でツールが出す警告の文言を、/root/tf/plan/out/target-warning.txtに残します。
変更が2つ以上待機しているときに、1つだけを狙います。狙うリソースは、他のリソースを参照しないものを選んでください。依存先が一緒に引き込まれます。
壊れた設定のエラーを読んで直す
/opt/lab/fixtures/terraform/broken/main.tfを/root/tf/plan/broken/main.tfにコピーして、そのまま実行し、エラーの出力を/root/tf/plan/out/broken.txtに保存してください(引数名contentsが問題だというメッセージが含まれている必要があります)。そのあと、同じファイルを/root/tf/plan/fixed/main.tfにコピーして、contentsを正しい引数contentに直し、直した設定のプランを、JSONで/root/tf/plan/out/fixed.jsonに保存します。
エラーメッセージが、引数名をそのまま教えてくれます。エラーの出力は標準エラーに出るので、ファイルに入れるときは、一緒に渡す必要があります。
破壊的な変更のレビューレポートを作る
最後に、/root/tf/planの設定から、リソースを1つ丸ごと削除して、削除が含まれるプランを作り、JSONで/root/tf/plan/out/final.jsonに保存してください。そのJSONを読んで、/root/tf/plan/out/review.jsonを作成します。add(動作にcreateが含まれる変更の数)、change(動作が["update"]の変更の数)、destroy(動作にdeleteが含まれる変更の数)の3つの数字と、削除対象のアドレスをすべて含むdestructive_addresses配列、そしてverdictをneeds-reviewとして入れてください。
要約行のadd・change・destroyと同じ基準で数えればよいです。置き換えは、addとdestroyの両方に捕捉されるという点と、削除対象のアドレスを1つも漏らしてはいけないという点に、注意してください。