(2/2)CI/CD GitHub Actionsでインフラを自動デプロイ!
今までの記事
- (1/2)CI/CD GitHub Actionsでインフラを自動デプロイ!
- 生成AI構成図作成!AWS製生成AIアプリ「Bedrock Engineer」の「Diagram Generator」を試してみた!!!
- (2/2)Terraformで作成してprd環境をstgとdevに拡張するお話
- (1/2)Terraformで作成してprd環境をstgとdevに拡張するお話
- TerraformでS3,CloudFront環境をアカウント跨ぎの移行
- (2/2)静的webサイトをS3にデプロイ!claude codeとGitHub Actionsを用いて自動デプロイ!
- (1/2)静的webサイトをS3にデプロイ!claude codeとGitHub Actionsを用いて自動デプロイ!
目次
前回の続きから
フェーズ3:feature push用にplanも追加したいのでその編集を開始
既存のファイル修正箇所
+ TFLINT_PLUGIN_DIR: /home/runner/.tflint.d/plugins
+ AWS_REGION: ap-northeast-1
+ HUB_ACCOUNT_ID: "318574063927"
+ STATE_BACKEND: "dev"
+ WORKDIR_DEV: "infra/static-website/environments/dev"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# name: Check for existing PR
- if (prs.length > 0) {
- console.log(`Found existing PR #${prs[0].number}`);
- return prs[0].number;
- }
- return '';
- result-encoding: string
+ core.setOutput('number', prs.length ? prs[0].number.toString() : '');
その他修正点
- 環境変数を出来るだけ使うようにした
- terraform fmf,validate,tflinkをdev環境にした
core.setOutput('number', prs.length ? prs[0].number.toString() : '');
-
core.setOutput():actions/giyhub-scriptが提供する@actions/coreライブラリのメソッド。
- GitHub Actionsのワークフロー内で値を出力する。
- 後ほど
steps.<id>.outputs.number
のような形で参照出来る。
- 後ほど
- GitHub Actionsのワークフロー内で値を出力する。
-
length:配列の要素数を表すプロパティ。
- 今回はps内に格納されている配列の要素数をカウント。
-
?:三項演算子でif文の省略系(<条件>?<真>:<偽>)
-
prs.length
は値が0とか値がない場合はfalseとなる。- なのでこの場合は
prs.length
が条件の役割を満たし、真と偽の処理に分かれる。
- なのでこの場合は
-
-
prs[0].number.toString():
prs[0]
が配列の最初の要素を取得し、.number
そのオブジェクトのnumberプロパティを取得し、,toString
その数値を文字列に変換- この一連が真の場合の処理。
-
'':空文字を表す。
- 今回の場合は偽の時の処理がこれになる。
planのための追加分の記載
# 👇 fmt/validate/tflint の outcome を後続ジョブへ渡す
- name: Collect step outcomes
id: collect
run: |
echo "fmt=${{ steps.fmt.outcome }}" >> "$GITHUB_OUTPUT"
echo "validate=${{ steps.validate.outcome }}" >> "$GITHUB_OUTPUT"
echo "tflint=${{ steps.tflint.outcome }}" >> "$GITHUB_OUTPUT"
# 👇 3つ全部 success のときだけ plan を許可するフラグを出力
- name: Decide plan gate
id: gate
run: |
ok=true
[ "${{ steps.fmt.outcome }}" = "success" ] || ok=false
[ "${{ steps.validate.outcome }}" = "success" ] || ok=false
[ "${{ steps.tflint.outcome }}" = "success" ] || ok=false
echo "lint_ok=$ok" >> "$GITHUB_OUTPUT"
-
outcomes:そのステップの実行結果を表す文字列。
-
steps.<step_id>.outcome
の形で参照出来て、取れる値はsuccess
/failure
/cancelled
/skipped
。
-
-
echo "fmt=${{ steps.fmt.outcome }}" >> "$GITHUB_OUTPUT"
:outcomeの出力をfmtをキーとして、$GITHUB_OUTPUT
へ追記している。 - ok=true:okの値はtrueとして初期設定をする
- [...]:testコマンド。
-
||:左側が失敗となったら右側を実行するシェルのOR演算子。
- 今回の場合は左の条件式が成立しなかたったら、右のok=falseを実行する。
-
echo "lint_ok=$ok" >> "$GITHUB_OUTPUT"
:lint_okをキーとし、okを変数として$GITHUB_OUTPUT
へ追記している。
# ---- dev だけ Terraform Plan(Hub→dev ロールチェーン)。lint成功時のみ ----
plan-dev:
needs: [lint-summary]
if: ${{ needs.lint-summary.outputs.lint_ok == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# ✅ Terraform を 1.9.2 でセットアップ
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
# ✅ jq をインストール(バージョン表示は任意)
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
echo "Using jq: $(jq --version)"
# (任意・安定化)キャッシュディレクトリを先に作成
- name: Create plugin cache dir
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
# 1) まずハブアカウントに OIDC で Assume
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-hub-${{ github.run_id }}-${{ github.actor }}
aws-region: ${{ env.AWS_REGION }}
# init(既定)
- name: Terraform Init (backend in DEV)
working-directory: ${{ env.WORKDIR_DEV }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
-
needs: [lint-summary]:
needs
は、このジョブを動かす前に完了していて欲しいジョブを宣言する場所。- 依存関係(実行順序)を作り、並列実行がデフォルトのジョブを順次実行に変えたり、前段ジョブの出力を参照出来るようにする。
-
if: ${{ needs.lint-summary.outputs.lint_ok == 'true' }}:GitHub ActionsのExpression構文で、文字列としてではなく式として評価→値に置き換えられる。
- ifなので参照先がtrueならこのジョブの実行をする。
- needs.lint-summary.outputs.lint_ok:依存ジョブのアウトプットのlint_okキーのものを参照する。
- id-token: write:Assumeロールするのに必要な権限。
- actions/checkout@v4:リポジトリのコードをランナー環境にチェックアウト(取得)するための公式アクション。
-
persist-credentials: false:actions/checkoutの入力オプションで、チェックアウト時に設定される認証情報(GITHUB_TOKENなど)をローカルのGit設定に残さない設定。
- デフォルトはtrueで残すと言う意味。
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
echo "Using jq: $(jq --version)"
- jq:JSONをコマンドラインツールで検索・変換・整形するためのフィルタ言語ツール。
-
apt:Debian/Ubuntu系のLinuxでパッケージをインストール・管理するパッケージマネージャー。
- ソフトウェアのインストール、更新、削除、依存関係の管理を自動化するためのツール。
- yumやnpmやHomebrewなどと同じ立ち位置。
- ソフトウェアのインストール、更新、削除、依存関係の管理を自動化するためのツール。
- sudo apt-get update:aptのアップデートを行う。
- sudo apt-get install -y jq:jqを非対話でインストールするコマンド。
- echo "Using jq: $(jq --version)":jqのバージョンを取得してログに出すためのコマンド。
ポイント
jqをインストールするために、aptをアップデートしてインストールして、バージョンをログとして出力させる。
-
aws-actions/configure-aws-credentials@v4:GitHubが発行するOIDC IDトークンを使い、AWS STSのAssumeRoleWithWebIdentityを実行する。
- 指定のロールを引き受けた一時クレデンシャルを環境変数にエクスポートする。
- これでaws cliやterraformなどはサインイン済みとして動作する。
- 指定のロールを引き受けた一時クレデンシャルを環境変数にエクスポートする。
-
AssumeRoleWithWebIdentity:AWS STSのAPI機能でOIDCなどのwebアイデンティティトークンを提示してIAMロールを一時的に引き受けるための仕組み。
- 長期アクセスキーを配らずに外部IDプロバイダ(GitHub OIDCなど)から来たワークロードに短期クレデンシャルを発行出来る。
- role-to-assume:Asuumeする際のロールのARN名。
- role-session-name:Assumeした際のセッション名で、このセッション名がTrailとかに記録される。
- aws-region:Assumeする際のリージョンを指定する。
上記1ブロック要約
Lint-summaryが終わったら始まるジョブで、ifの条件がlint_okのアウトプットがtrueであること。
つまりfmf,varidate,tflinkがsuccessならばジョブを開始する。
今回planも実行するので、id-tokenの権限を付与する。
terraform showはJSONで出力されるので、それを編集するためのjqをインストールし
terraformをセットアップし、OIDCでAssumeするための記述も行っている。
- name: Terraform Plan (dev)
id: tfplan
working-directory: ${{ env.WORKDIR_DEV }}
run: |
set -eo pipefail
terraform plan -no-color -input=false -lock-timeout=60s -out=tf.plan | tee plan.txt
terraform show -json tf.plan > tfplan.json
creates=$(jq '[.resource_changes[]? | select(.change.actions | index("create"))] | length' tfplan.json)
replaces=$(jq '[.resource_changes[]? | select(.change.actions | index("replace"))] | length' tfplan.json)
updates=$(jq '[.resource_changes[]? | select(.change.actions | index("update"))] | length' tfplan.json)
deletes=$(jq '[.resource_changes[]? | select(.change.actions | index("delete"))] | length' tfplan.json)
creates=$((creates + replaces))
deletes=$((deletes + replaces))
echo "creates=$creates" >> $GITHUB_OUTPUT
echo "updates=$updates" >> $GITHUB_OUTPUT
echo "deletes=$deletes" >> $GITHUB_OUTPUT
- -input=false:対話入力を無効化するもの。
-
-lockfile=readonly:ロックファイルを書き換え無い設定。
- 書き換えてしまったら想定と違う状態でinitされるし、書き換えたとしてもその反映をローカルに持ち帰れる訳では無い。
-
-upgrade=false:既存のロックファイル内でinitをする。
- アップグレードをしない。
- set -e:
-
set -eo pipefail:そのシェル内で、このコマンド以降にエラーや失敗が起こると動作を停止させるコマンド。
- GitHub Actions内なら
run:
ステップの中だけで有効。-
set
:シェルの動作オプションを切り替え -
-e
:スクリプト内で実行される任意のコマンド失敗した場合にスクリプト全体を終了させる。-
|
パイプの処理だと、最後の処理が成功すれば問題ないとみなされる。
-
-
-o pipefail
:|
で繋ぐパイプライン内のいずれかのコマンドが失敗した場合に全体のパイプラインを失敗扱いにする。
-
- GitHub Actions内なら
terraform plan -no-color -input=false -lock-timeout=60s -out=tf.plan | tee plan.txt
-
-no-color:カラー情報を入れないオプション。
- ログを見る時は見にくくなる。
- -input=false:非対話での実行を指定する。
- -lock-timeout=60s:stateロックが掛かっていても60秒待機する。
-
-out=tf.plan:人間には読めないplan結果のバイナリファイルを出力させる。
- これをレビューすればplan→レビューapplyのズレが最小限になる。
-
| tee plan.txt:
-out=tf.plan
の出力結果を、画面に出力させながらplan.txtへ書き込む。-
|
:前のコマンドの標準出力をteeへ受け渡す -
tee
:画面に表示しつつ、同時にファイルへの書き込みも行う。
-
ポイント
terraformの実行計画を立てるコマンドで、色情報は抜いて、非対話で、ロック待機60秒で、planのバイナリファイルをplan.txtへ書き込む。
terraform show -json tf.plan > tfplan.json
-
terraform show:Terraformの表示コマンド。
- planファイルやstateを人間が読めたり、機械が読みやすいように整形して出力する。
-
-json:
terraform show
のオプションで、出力を機械可読可能なJSON形式にする。 - tf.plan:この引数がなければ現在のstateから出力するが、引数を指定いるためtf.planから出力する。
-
>:リダイレクト記号で、指定のファイルに上書きする。
-
>>
は追記。
-
- tfplan.json:出力先ファイル名
ポイント
terraformのバイナリファイルや、stateを出力形式を決めて出力出来て、前回コマンドで出力したplanのバイナリファイルを読み込んでJSON形式で出力させる。
それをtfplan.jsonがあれば上書きで、なければ作成する。
creates=$(jq '[.resource_changes[]? | select(.change.actions | index("create"))] | length' tfplan.json)
replaces=$(jq '[.resource_changes[]? | select(.change.actions | index("replace"))] | length' tfplan.json)
updates=$(jq '[.resource_changes[]? | select(.change.actions | index("update"))] | length' tfplan.json)
deletes=$(jq '[.resource_changes[]? | select(.change.actions | index("delete"))] | length' tfplan.json)
creates=$((creates + replaces))
deletes=$((deletes + replaces))
-
creates=$():
()
内の計算出力を代入する。 -
jq:jqコマンドの開始。
- jqはJSONクエリ言語。
- '[...]':jqのフィルタ式(シングルクォートで囲む)
-
.resource_changes[]?:トップオブジェクトから
resource_changes
キーを検索し、その中の要素を繰り返し取り出す。
もし配列が空でも、エラーとならずに空配列として出力する。-
.
jqのデフォルトコンテキストで、クエリの開始地点としてJSON全体(ルートオブジェクト)を指す。- 明示的に書いていないが、
.resource_changes
はルートからの相対パス。
- 明示的に書いていないが、
-
resource_changes
JSONファイル内のトップレベルのキー。 -
[]
配列イテレータって名前のjqのフィルタ内で動作するjqの演算子。- 配列の各要素を1つずつ取り出し、ストリームとして出力。
- ストリーム処理は1個ずつ受け渡す。
- 配列の各要素を1つずつ取り出し、ストリームとして出力。
-
?
配列がからでもエラーとならずに空配列を出力。 -
|
は、左の出力の値を右へ受け渡す。
-
-
select(.change.actions | index("create")):
-
select(...)
:は中の値を評価し、真ならその要素を残し、偽なら捨てる。- jqではfalseとnull以外は真(数値の0も真)。
-
.
:今回で言うと、resource_changesの中から抽出した要素の中のパスが一番上からって意味。 -
change.actions
:changeオブジェクトのactionsのフィールドを抽出してる。 -
|
:は左の出力結果を右に受け渡す。 -
index("create")
:actions
のフィールドの中をindexして先頭から何番目にあるかの値を出力する。- 無ければnullを返す。
- nullを返すのでストリーム処理で1要素ずつ、真で残るか、偽で捨てられるか決まる。
- 無ければnullを返す。
-
-
| length' tfplan.json:
-
|
:左の値を右に受け渡す。 -
length
:配列の要素すうをカウントする関数。 -
'
:jqに指示しているフィルタ部分の終端。
-
-
terraformのステータス:
-
create
:新規作成。- 新しいリソースを作成した状態。
-
replace
:- 既存のリソースを削除して、作成した状態。
-
update
:そのまま更新。- 既存のリソースの変更が不要な、属性変更の状態。
-
delete
:削除。- リソースを削除した状態。
-
- creates=$((creates + replaces)):リプレイスは既存リソースを削除して作成なので、作成にカウントするために足して、それをcreatesへ代入する。
- deletes=$((deletes + replaces)):リプレイスは既存リソースを削除して作成なので、削除にもカウントするために足してdeletesへ代入する。
-
echo "creates=$creates" >> $GITHUB_OUTPUT
:createsをキーとして、 GITHUB_OUTPUT`へ出力する。createsを`
上記1ブロック要約
エラーが発生する時点で処理が止まるように設定し、処理を開始。
planでの出力は色なし、非対話、ロック待機60秒、でplan結果のバイナリファイルをtf.planに出力し、その出力をplan.txtへ書き込む。
terraform showでplanのバイナリファイルをJSON形式でtfplan.jsonへ書き込む。
そのjsonファイルをjqでクエリをする。内容はresource_changesキー内の要素の数だけ繰り返し処理が行われて、change内のactionsのフィールドをindexで検索しnullならselectで捨てる。その配列の要素数をカウントして、変数へ代入する。
変数replacesは作成と削除に対応するので、どちらにもカウントする。
その後それぞれを$GITHUB_OUTPUT
へ追記する形で出力する。
- name: Build plan comment (dev)
id: body
working-directory: ${{ env.WORKDIR_DEV }}
run: |
{
echo "### Terraform Plan \`dev\` (+${{ steps.tfplan.outputs.creates }} / ~${{ steps.tfplan.outputs.updates }} / -${{ steps.tfplan.outputs.deletes }})"
echo
echo "**Base:** \`main\` | **Ref:** \`${GITHUB_SHA::7}\` | **Dir:** \`${{ env.WORKDIR_DEV }}\`"
echo
echo "<details><summary>Show full plan</summary>"
echo
echo '```'
cat plan.txt
echo '```'
echo
echo "</details>"
} > body.md
- name: Find existing PR (for comment)
id: find-pr
uses: actions/github-script@v7
with:
script: |
const branch = context.ref.replace('refs/heads/', '');
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:${branch}`,
base: 'main',
state: 'open'
});
core.setOutput('number', prs.length ? prs[0].number.toString() : '');
- name: Comment plan to PR (dev)
if: steps.find-pr.outputs.number != ''
run: gh pr comment ${{ steps.find-pr.outputs.number }} --body-file "${{ env.WORKDIR_DEV }}/body.md"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Attach plan snippet to Job Summary (dev)
run: |
echo "## 🧮 Plan dev (+${{ steps.tfplan.outputs.creates }} / ~${{ steps.tfplan.outputs.updates }} / -${{ steps.tfplan.outputs.deletes }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Show full plan</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,400p' "${{ env.WORKDIR_DEV }}/plan.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
-
} > body.md:
{...}
内の記載をbody.mdへ上書きで書き込み。 -
const branch = context.ref.replace('refs/heads/', '');:ブランチ名をbranch変数へ格納する。
-
replace
メソッドで、文字列の先頭からrefs/heads/
を空文字に置き換えることで実質的に削除を行う。- そのことによってブランチ名だけを取り出す。
-
-
const { data: prs } = await github.rest.pulls.list({:引数で指定しているPRの一覧を取得して、その中のdataプロパティを変数prsへ格納する。
- それをconstで定数としている。
-
github.rest.pulls.list
:actions/github-script@v7
で呼び出せる機能の1つで、PRの一覧を取得する。- 引数はオーナー名、リポジトリ名、ここからここ宛のPRでオープンになってるものを指定している。
-
-
data: prs
はその中のdetaプロパティをprs変数へ格納している。
- それをconstで定数としている。
-
core.setOutput('number', prs.length ? prs[0].number.toString() : '');:
-
core.setOutput()
:GitHub Actionsの@actions/coreライブラリが提供する関数で、現在のステップから他のステップへデータを渡すために使用する。 -
'number'
:出力変数名のキー。 -
prs.length
:PRの配列の要素数カウント。 -
?
:三項演算子。条件 ? 真の場合の値 : 偽の場合の値
-
prs.length
が0の時は偽と評価される。
-
-
prs[0].number.toString()
:真の時はprsの最初のPRの.numberプロパティを文字列に変換して出力。- 偽の時は空文字を出力。
-
-
if: steps.find-pr.outputs.number != '':find-prステップの出力が空文字では無い場合が真。
- つまりPRがある状態が真として処理を開始する。
run: gh pr comment ${{ steps.find-pr.outputs.number }} --body-file "${{ env.WORKDIR_DEV }}/body.md"
- gh prコメント
- gh:GitHub CLIのコマンド。
-
pr:
gh
のサブコマンドで、『Pull Requestに関する操作』を使う宣言。 - comment:さらにその下のサブコマンドで、『PRにコメントを書く』と言う意味。
-
${{ steps.find-pr.outputs.number }}
:PRのナンバーを指定する。- ghコマンドは自動的に現在のリポジトリを検出するので、ナンバーを指定するだけで良い。
-
--body-file:コメントの内容をファイルから読み込むためのオプション。
- この記述がないと直接のコメント記載となる。(
gh pr comment 123 --body "これはコメントです"
)
- この記述がないと直接のコメント記載となる。(
-
"${{ env.WORKDIR_DEV }}/body.md"
:ファイルがあるパスを記載する。
ポイント
GitHub CLIでプルリクにコメントを記載する
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
:-
GITHUB_TOKEN
ghコマンドが認証としてGITHUB_TOKENを探すので、GITHUB_TOKEN変数とする。 -
secrets.GITHUB_TOKEN
GitHub Actionsのワークフロー実行時に認証情報が渡される。- それをghコマンドが使える形に変数へ代入する。
- envとrunの順番どちらでもOK。
- ステップの順番は気を付ける必要がある。
-
-
sed -n '1,400p' "${{ env.WORKDIR_DEV }}/plan.txt" >> $GITHUB_STEP_SUMMARY
:-
sed
:テキストをストリーム処理するためのコマンド。- ファイルを開かずとも編集できる。
-
-n
:自動出力を抑止し、明示的に出力指示した行だけを出すことが出来る。 -
1,400
:1〜400行を指定 -
p
:その範囲を出力する -
"${{ env.WORKDIR_DEV }}/plan.txt"
:処理するファイルのパス。 -
>> $GITHUB_STEP_SUMMARY
:GITHUB_STEP_SUMMARYへリダイレクト(追記)。
-
上記1ブロック要約
プラン結果のcreates、updates、deletesの数を出力する。
次にPRが存在すれば、そのコメントにプラン結果を出力する。
comment-why-skip:
needs: [lint-summary]
if: ${{ needs.lint-summary.outputs.lint_ok != 'true' && needs.lint-summary.outputs.pr_number != '' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Explain why plan was skipped
run: |
gh pr comment ${{ needs.lint-summary.outputs.pr_number }} --body "$(cat <<'MD'
### ⏭️ Plan skipped
Lint/Validate/TFLint のいずれかが失敗したため、この push では **Terraform plan を実行していません**。
- Format: ${{ needs.lint-summary.outputs.fmt }}
- Validate: ${{ needs.lint-summary.outputs.validate }}
- TFLint: ${{ needs.lint-summary.outputs.tflint }}
失敗箇所を修正後、再 push してください。
MD
)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- needs: [lint-summary]:lint-summaryステップが終わるまでジョブは走らない。
if: ${{ needs.lint-summary.outputs.lint_ok != 'true' && needs.lint-summary.outputs.pr_number != '' }}
- if:この条件が偽ならこのジョブを実行しないと言う条件分岐。
-
needs.lint-summary.outputs.lint_ok:lint-summaryステップのアウトプットを参照している。
-
needs
:依存関係にある他のジョブを参照する。 -
lint-summary
:参照するジョブ名。 -
outputs
:そのジョブが出力した値。 -
lint_ok
:出力の変数名
-
-
!= 'true':trueじゃないのが真。
- つまりfmt、validate、tflintのどれかがfalseなら真になる。
- &&:ifの条件が『fmt、validate、tflintが失敗』かつ『PRが存在する』場合って言う、2つの条件を満たした時。
ポイント
fmt、validate、tflintが失敗した時かつ、PRが存在する場合にジョブを実行する。
処理の流れ
ランナーはrun:
ブロックの文字列を受け取り、まず${{...}}(Actionsの式)を置換する。
この時点では、まだシェルは動いていない。あくまで文字列の前処理。
ヒアドキュメントでは、区切り語をクォートしてるかどうかで、bashが本文に対して行う展開処理が切り替わる。
- <<:ヒアドキュメントを始める合図で、複数行の文字列をそのままコマンドの標準入力に渡すために使える
- MD:区切り語。ヒアドキュメントの開始。
- "$(...)":コマンド置換の記法。さらにダブルクォートで囲むと改行してもコマンドの区切りにはならない。
-
'...':今回はシングルクォートで囲ってるので、変数展開せずに出力される。
- ただし${{...}}のようなGitHub Actionsの式展開はされる。
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
:ghコマンドのための認証を使えるようにGITHUB_TOKEN
変数に格納する。
上記1ブロック要約
『fmt、validate、tflintが失敗』かつ『PRが存在する』場合にPRに対して失敗した理由をコメントする。
feature push用の既存のコード修正とplan実行を追加したのをテスト
Error: Cannot assume IAM Role
with provider["registry.terraform.io/hashicorp/aws"].dns_master,
on providers.tf line 23, in provider "aws":
23: provider "aws" {
IAM Role (arn:aws:iam::252170044718:role/Route53CrossAccountManagerRole)
cannot be assumed.
There are a number of possible causes of this - the most common are:
* The credentials used in order to assume the role are invalid
* The credentials do not have appropriate permission to assume the role
* The role ARN is not valid
Error: operation error STS: AssumeRole, https response error StatusCode: 403,
上記エラー内容
踏み台アカウントでGitHubとAssumeロールを作成したが、DNS管理アカウントと信頼関係を結んでいなかった。
DNSアカウントロールの信頼されたエンティティにGitHubロールを追加。
GitHubロールにAssumeを許可するロールにDNSアカウントロールを追加。
2回目feature push用の既存のコード修正とplan実行を追加したのをテスト
🔍 Terraform Check Results\n\n| Check | Status |\n|-------|--------|\n| Format | ✅ Pass |\n| Validate | ✅ Pass |\n| TFLint | ✅ Pass |\n
summaryの文字が崩れている。
Bash はダブルクォート内の \n を改行に展開されないのでechoで1行ずつ挿入し、catの出力にするように修正。
run: |
{
echo "### 🔍 Terraform Check Results"
echo
echo "| Check | Status |"
echo "|-------|--------|"
if [ "${{ steps.fmt.outcome }}" = "success" ]; then
echo "| Format | ✅ Pass |"
else
echo "| Format | ❌ Fail |"
fi
if [ "${{ steps.validate.outcome }}" = "success" ]; then
echo "| Validate | ✅ Pass |"
else
echo "| Validate | ⚠️ Warning |"
fi
if [ "${{ steps.tflint.outcome }}" = "success" ]; then
echo "| TFLint | ✅ Pass |"
else
echo "| TFLint | ⚠️ Warning |"
fi
} > summary.md
{
echo "summary<<EOF"
cat summary.md
echo "EOF"
} >> "$GITHUB_OUTPUT"
3回目feature push用の既存のコード修正とplan実行を追加したのをテスト
Error: Unhandled error: HttpError: Resource not accessible by integration
PRへのコメントの箇所でエラー。
pull-requests権限をreadにしていたので、writeに修正
4回目feature push用の既存のコード修正とplan実行を追加したのをテスト
Run gh pr comment 13 --body-file "infra/static-website/environments/dev/body.md"
GraphQL: Resource not accessible by integration (addComment)
Error: Process completed with exit code 1.
やはり権限エラー。
pull-requestsが読み取り権限のみになってたので修正。
5回目feature push用の既存のコード修正とplan実行を追加したのをテスト
成功した!
infra配下の変更でfeature/*からpushすると
fmf,validate,tflink,を実施して、summaryとPRのコメントへチェック結果を記載。
そしてplanも実施してplan結果をsummaryとPRのコメントへ結果を記載。
フェーズ4:codexで/infra変更でmainのpushワークフローを作成
terraform-deploy-dev-on-main.yml
name: Terraform Deploy dev on main
on:
push:
branches: ['main']
paths:
- 'infra/**'
- '.terraform-version'
- '.github/workflows/terraform-deploy-dev-on-main.yml'
permissions:
contents: read
id-token: write
env:
TF_PLUGIN_CACHE_DIR: /home/runner/.terraform.d/plugin-cache
AWS_REGION: ap-northeast-1
HUB_ACCOUNT_ID: "318574063927"
WORKDIR_DEV: "infra/static-website/environments/dev"
concurrency:
group: deploy-dev-main
cancel-in-progress: false
jobs:
plan-apply-dev:
name: Plan + Apply (dev)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform 1.9.2
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Prepare plugin cache
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-deploy-dev-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init (dev backend)
working-directory: ${{ env.WORKDIR_DEV }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
- name: Terraform Plan (dev)
id: plan
working-directory: ${{ env.WORKDIR_DEV }}
shell: bash
run: |
set -eo pipefail
terraform plan -no-color -input=false -lock-timeout=60s -out=tf.plan | tee plan.txt
terraform show -json tf.plan > tfplan.json
creates=$(jq '[.resource_changes[]? | select(.change.actions | index("create"))] | length' tfplan.json)
replaces=$(jq '[.resource_changes[]? | select(.change.actions | index("replace"))] | length' tfplan.json)
updates=$(jq '[.resource_changes[]? | select(.change.actions | index("update"))] | length' tfplan.json)
deletes=$(jq '[.resource_changes[]? | select(.change.actions | index("delete"))] | length' tfplan.json)
creates=$((creates + replaces))
deletes=$((deletes + replaces))
echo "creates=$creates" >> "$GITHUB_OUTPUT"
echo "updates=$updates" >> "$GITHUB_OUTPUT"
echo "deletes=$deletes" >> "$GITHUB_OUTPUT"
- name: Terraform Apply (dev)
id: apply
working-directory: ${{ env.WORKDIR_DEV }}
continue-on-error: true
shell: bash
run: |
set -o pipefail
terraform apply -no-color -input=false -auto-approve tf.plan 2>&1 | tee apply.txt
echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
- name: Add Job Summary
if: always()
shell: bash
run: |
echo "## 🚀 Terraform Deploy to dev (main)" >> $GITHUB_STEP_SUMMARY
echo "- Commit: \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
echo "- Actor: @${GITHUB_ACTOR}" >> $GITHUB_STEP_SUMMARY
echo "- Directory: \`${{ env.WORKDIR_DEV }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_DEV }}/plan.txt" ]; then
echo "### 🧮 Plan Summary" >> $GITHUB_STEP_SUMMARY
echo "- + Create: ${{ steps.plan.outputs.creates }}" >> $GITHUB_STEP_SUMMARY
echo "- ~ Update: ${{ steps.plan.outputs.updates }}" >> $GITHUB_STEP_SUMMARY
echo "- - Delete: ${{ steps.plan.outputs.deletes }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Show full plan</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,400p' "${{ env.WORKDIR_DEV }}/plan.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
status="success"
if [ "${{ steps.apply.outputs.exit_code }}" != "0" ]; then
status="failure"
fi
echo "### ✅ Apply Result: ${status}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_DEV }}/apply.txt" ]; then
echo "<details><summary>Show apply log</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,300p' "${{ env.WORKDIR_DEV }}/apply.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
- name: Fail job if apply failed
if: ${{ steps.apply.outputs.exit_code != '0' }}
run: |
echo "Apply failed with exit code: ${{ steps.apply.outputs.exit_code }}" >&2
exit 1
GitHubActionsのワークフローを作成したい。
infra配下の変更で、mainにマージ、pushされると、dev環境にplan applyを実行するワークフローを作成したい。
その結果をSummaryに記載したい。
codexのVS CODEのIDEでチャットに打ち込む。
-
set -eo pipefail:パイプの途中で失敗しても即終了出来るようにしている。
-
set -e
:どれか1つでもコマンドが失敗したらスクリプトを終了させる。 -
set -o pipefail
:|
パイプラインが途中で失敗したら、きちんとスクリプトを終了させる。
-
-
echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
:-
exit_code
:コマンドが終了した時に返すステータス番号を表す変数。 -
PIPESTATUS
:直前に実行したパイプラインの各コマンドの終了コードを左から順に入れている。
-
- tee:画面に出力して、ファイルにも出力。
-
2>&1:標準エラーの出力先を標準出力と同じ場所へ向ける。
-
2
:標準エラー出力。 -
1
:標準出力。 -
&
:1を標準出力として認識するための記載。
-
-
if [ -f "${{ env.WORKDIR_DEV }}/plan.txt" ];:
-
[...]
:テストコマンドと同義。 -
-f
:は通常ファイルが存在するか。- 通常ファイルが存在すれば真となる。
-
infra変更でmainのpushワークフローを作成の機能要約
infra/配下の変更でmainにpushやマージが起きた時にワークフローが実行する。
Terraformのセットアップとjqのインストール、cacheのディレクトリ作成とcacheの設定をする。
HUBアカウントへAssumeをしてterraform initをする。
その次にterraform planを実施し、plan結果をJSONファイルに保存し、plan結果を集計し、GITHUB_OUTPUTへ出力させる。
その次にterraform applyをする。
SUMMARYにplan結果を出力。
applyの結果がエラーならエラーの出力をする。
フェーズ5:codex cloudでRCタグ昇格でstgへのワークフロー作成
terraform-deploy-stg-on-tag.yml
name: Terraform Deploy stg on tag
on:
push:
tags:
- 'infra/v*.*.*-rc*'
permissions:
contents: read
id-token: write
env:
TF_PLUGIN_CACHE_DIR: /home/runner/.terraform.d/plugin-cache
AWS_REGION: ap-northeast-1
HUB_ACCOUNT_ID: "318574063927"
WORKDIR_STG: "infra/static-website/environments/stg"
concurrency:
group: deploy-stg
cancel-in-progress: false
jobs:
plan:
name: Plan (stg)
runs-on: ubuntu-latest
outputs:
creates: ${{ steps.plan.outputs.creates }}
updates: ${{ steps.plan.outputs.updates }}
deletes: ${{ steps.plan.outputs.deletes }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform 1.9.2
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Prepare plugin cache
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-plan-stg-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init (stg backend)
working-directory: ${{ env.WORKDIR_STG }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
- name: Terraform Plan (stg)
id: plan
working-directory: ${{ env.WORKDIR_STG }}
shell: bash
run: |
set -eo pipefail
terraform plan -no-color -input=false -lock-timeout=60s -out=tf.plan | tee plan.txt
terraform show -json tf.plan > tfplan.json
creates=$(jq '[.resource_changes[]? | select(.change.actions | index("create"))] | length' tfplan.json)
replaces=$(jq '[.resource_changes[]? | select(.change.actions | index("replace"))] | length' tfplan.json)
updates=$(jq '[.resource_changes[]? | select(.change.actions | index("update"))] | length' tfplan.json)
deletes=$(jq '[.resource_changes[]? | select(.change.actions | index("delete"))] | length' tfplan.json)
creates=$((creates + replaces))
deletes=$((deletes + replaces))
echo "creates=$creates" >> "$GITHUB_OUTPUT"
echo "updates=$updates" >> "$GITHUB_OUTPUT"
echo "deletes=$deletes" >> "$GITHUB_OUTPUT"
- name: Upload plan artifacts
uses: actions/upload-artifact@v4
with:
name: tf-plan
path: |
${{ env.WORKDIR_STG }}/tf.plan
${{ env.WORKDIR_STG }}/plan.txt
- name: Add Job Summary
if: always()
shell: bash
run: |
ref_name="${GITHUB_REF_NAME:-${GITHUB_REF#refs/*/}}"
plan_outcome="${{ steps.plan.outcome }}"
creates="${{ steps.plan.outputs.creates }}"
updates="${{ steps.plan.outputs.updates }}"
deletes="${{ steps.plan.outputs.deletes }}"
creates=${creates:-0}
updates=${updates:-0}
deletes=${deletes:-0}
plan_outcome=${plan_outcome:-failure}
echo "## 🔍 stg 向け Terraform Plan" >> $GITHUB_STEP_SUMMARY
echo "- タグ: \`${ref_name}\`" >> $GITHUB_STEP_SUMMARY
echo "- コミット: \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
echo "- 実行ユーザー: @${GITHUB_ACTOR}" >> $GITHUB_STEP_SUMMARY
echo "- ディレクトリ: \`${{ env.WORKDIR_STG }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$plan_outcome" = "success" ]; then
echo "### ✅ Plan 成功" >> $GITHUB_STEP_SUMMARY
else
echo "### ❌ Plan 失敗" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🧮 変更サマリ" >> $GITHUB_STEP_SUMMARY
echo "- + 作成: ${creates}" >> $GITHUB_STEP_SUMMARY
echo "- ~ 更新: ${updates}" >> $GITHUB_STEP_SUMMARY
echo "- - 削除: ${deletes}" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_STG }}/plan.txt" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Plan を表示</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,400p' "${{ env.WORKDIR_STG }}/plan.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
apply:
name: Apply (stg)
needs:
- plan
runs-on: ubuntu-latest
environment: infra-stg
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform 1.9.2
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Prepare plugin cache
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
- name: Download plan artifacts
uses: actions/download-artifact@v4
with:
name: tf-plan
path: ${{ env.WORKDIR_STG }}
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-apply-stg-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init (stg backend)
working-directory: ${{ env.WORKDIR_STG }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
- name: Terraform Apply (stg)
id: apply
working-directory: ${{ env.WORKDIR_STG }}
continue-on-error: true
shell: bash
run: |
set -o pipefail
terraform apply -no-color -input=false -auto-approve tf.plan 2>&1 | tee apply.txt
echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
- name: Add Job Summary
if: always()
shell: bash
run: |
ref_name="${GITHUB_REF_NAME:-${GITHUB_REF#refs/*/}}"
creates="${{ needs.plan.outputs.creates }}"
updates="${{ needs.plan.outputs.updates }}"
deletes="${{ needs.plan.outputs.deletes }}"
exit_code="${{ steps.apply.outputs.exit_code }}"
creates=${creates:-0}
updates=${updates:-0}
deletes=${deletes:-0}
exit_code=${exit_code:-N/A}
echo "## 🚀 stg への Terraform 適用 (タグ)" >> $GITHUB_STEP_SUMMARY
echo "- タグ: \`${ref_name}\`" >> $GITHUB_STEP_SUMMARY
echo "- コミット: \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
echo "- 実行ユーザー: @${GITHUB_ACTOR}" >> $GITHUB_STEP_SUMMARY
echo "- ディレクトリ: \`${{ env.WORKDIR_STG }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🧮 Plan 変更サマリ" >> $GITHUB_STEP_SUMMARY
echo "- + 作成: ${creates}" >> $GITHUB_STEP_SUMMARY
echo "- ~ 更新: ${updates}" >> $GITHUB_STEP_SUMMARY
echo "- - 削除: ${deletes}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$exit_code" = "0" ]; then
echo "### ✅ Apply 成功" >> $GITHUB_STEP_SUMMARY
else
echo "### ❌ Apply 失敗" >> $GITHUB_STEP_SUMMARY
fi
echo "- 終了コード: ${exit_code}" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_STG }}/apply.txt" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Apply ログを表示</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,300p' "${{ env.WORKDIR_STG }}/apply.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
- name: Fail job if apply failed
if: ${{ steps.apply.outputs.exit_code != '0' }}
run: |
echo "Terraform Apply が終了コード ${{ steps.apply.outputs.exit_code }} で失敗しました" >&2
exit 1
codex cloudでのプロンプト
GitHubActionsを作成して欲しい
v*.*.* → prdにデプロイ(plan→手動承認→apply)
Suumaryにも結果を記載して欲しい
codex cloudでチャットに打ち込み
承認を挟むのでGitHubの設定
リポジトリの設定から設定し、このnameをGitHub Actions内で記述することとなる。
リポジトリ → Settings → Environments → New environment
コードの内容
-
outcome:そのステップのGitHub Actionsの実行結果を表す文字列。
-
success
:runが正常終了し、アクションが成功。 -
failure
:コマンド失敗し、アクション内部でエラーとなっている。 -
cancelled
:手動でワークフローをキャンセル。 -
skipped
:何かの条件で、実行されてなかった。
-
-
plan_outcome=${plan_outcome:-failure}
:plan_outcomeに値が無ければfailureを値として代入する。- シェルのパラメータ展開の演算子。
-
${...=...}
:右の値を左の値に代入する。- 値が未設定なら、代入。
- 値が空文字なら、代入しない。
- 値があったら、代入しない
-
${...=:...}
:右の値を左の値に、空文字の時も代入する- 値が未設定なら、代入。
- 値が空文字でも代入する。
- 値があったら代入しない。
-
:
は空文字の時どうするか。
-
${...-...}
:- 値が未設定は、代入せずに右の値を使う。
- 値が空文字は、空文字を使う。
-
${...-:...}
:- 値が未設定は、代入せずに右の値を使う。
- 値が空文字でも右の値を使う。
-
actions/upload-artifact@v4:Run間での受け渡しのための保管領域に保管する。
- 環境変数では、ステップ間でしか共有出来ない。
- サイズ制限も存在する。
- 環境変数では、ステップ間でしか共有出来ない。
- actions/download-artifact@v4:terraform applyの際に、planで計画した計画でapplyするためで、こうすることで一貫性が保てる。
1回目テスト実行
タグの付与。
tano1:0603game tano$ git tag -a v1.0.0-rc.1 -m "first release candidate"
tano1:0603game tano$ git push origin v1.0.0-rc.1
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 173 bytes | 173.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/YusukeTano/0603game-tano-public.git
* [new tag] v1.0.0-rc.1 -> v1.0.0-rc.1
tano1:0603game tano$
成功したので、次へ。
フェーズ6:claude codeでタグ昇格でprdへのワークフロー
terraform-deploy-prd-on-tag.yml
name: Terraform Deploy prd on tag
on:
push:
tags:
- 'infra/v[0-9]+.[0-9]+.[0-9]+' # より厳密なパターン
- '!v*-*' # プレリリース版を除外
permissions:
contents: read
id-token: write
env:
TF_PLUGIN_CACHE_DIR: /home/runner/.terraform.d/plugin-cache
AWS_REGION: ap-northeast-1
HUB_ACCOUNT_ID: "318574063927"
WORKDIR_PRD: "infra/static-website/environments/prd"
concurrency:
group: deploy-prd
cancel-in-progress: false
jobs:
plan:
name: Plan (prd)
runs-on: ubuntu-latest
outputs:
creates: ${{ steps.plan.outputs.creates }}
updates: ${{ steps.plan.outputs.updates }}
deletes: ${{ steps.plan.outputs.deletes }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform 1.9.2
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Prepare plugin cache
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-plan-prd-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init (prd backend)
working-directory: ${{ env.WORKDIR_PRD }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
- name: Terraform Plan (prd)
id: plan
working-directory: ${{ env.WORKDIR_PRD }}
shell: bash
run: |
set -eo pipefail
terraform plan -no-color -input=false -lock-timeout=60s -out=tf.plan | tee plan.txt
terraform show -json tf.plan > tfplan.json
creates=$(jq '[.resource_changes[]? | select(.change.actions | index("create"))] | length' tfplan.json)
replaces=$(jq '[.resource_changes[]? | select(.change.actions | index("replace"))] | length' tfplan.json)
updates=$(jq '[.resource_changes[]? | select(.change.actions | index("update"))] | length' tfplan.json)
deletes=$(jq '[.resource_changes[]? | select(.change.actions | index("delete"))] | length' tfplan.json)
creates=$((creates + replaces))
deletes=$((deletes + replaces))
echo "creates=$creates" >> "$GITHUB_OUTPUT"
echo "updates=$updates" >> "$GITHUB_OUTPUT"
echo "deletes=$deletes" >> "$GITHUB_OUTPUT"
- name: Upload plan artifacts
uses: actions/upload-artifact@v4
with:
name: tf-plan-prd
path: |
${{ env.WORKDIR_PRD }}/tf.plan
${{ env.WORKDIR_PRD }}/plan.txt
- name: Add Job Summary
if: always()
shell: bash
run: |
ref_name="${GITHUB_REF_NAME:-${GITHUB_REF#refs/*/}}"
plan_outcome="${{ steps.plan.outcome }}"
creates="${{ steps.plan.outputs.creates }}"
updates="${{ steps.plan.outputs.updates }}"
deletes="${{ steps.plan.outputs.deletes }}"
creates=${creates:-0}
updates=${updates:-0}
deletes=${deletes:-0}
plan_outcome=${plan_outcome:-failure}
echo "## 🔍 prd 向け Terraform Plan" >> $GITHUB_STEP_SUMMARY
echo "- タグ: \`${ref_name}\`" >> $GITHUB_STEP_SUMMARY
echo "- コミット: \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
echo "- 実行ユーザー: @${GITHUB_ACTOR}" >> $GITHUB_STEP_SUMMARY
echo "- ディレクトリ: \`${{ env.WORKDIR_PRD }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$plan_outcome" = "success" ]; then
echo "### ✅ Plan 成功" >> $GITHUB_STEP_SUMMARY
else
echo "### ❌ Plan 失敗" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🧮 変更サマリ" >> $GITHUB_STEP_SUMMARY
echo "- + 作成: ${creates}" >> $GITHUB_STEP_SUMMARY
echo "- ~ 更新: ${updates}" >> $GITHUB_STEP_SUMMARY
echo "- - 削除: ${deletes}" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_PRD }}/plan.txt" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Plan を表示</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,400p' "${{ env.WORKDIR_PRD }}/plan.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
apply:
name: Apply (prd)
needs:
- plan
runs-on: ubuntu-latest
environment: infra-prd
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform 1.9.2
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.2
- name: Install jq
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Prepare plugin cache
run: mkdir -p "${{ env.TF_PLUGIN_CACHE_DIR }}"
- name: Cache Terraform plugins
uses: actions/cache@v4
with:
path: ${{ env.TF_PLUGIN_CACHE_DIR }}
key: ${{ runner.os }}-tfplugins-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: ${{ runner.os }}-tfplugins-
- name: Download plan artifacts
uses: actions/download-artifact@v4
with:
name: tf-plan-prd
path: ${{ env.WORKDIR_PRD }}
- name: Assume Hub (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.HUB_ACCOUNT_ID }}:role/GitHubActionsHubRole
role-session-name: gha-apply-prd-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init (prd backend)
working-directory: ${{ env.WORKDIR_PRD }}
run: terraform init -input=false -lockfile=readonly -upgrade=false
- name: Terraform Apply (prd)
id: apply
working-directory: ${{ env.WORKDIR_PRD }}
continue-on-error: true
shell: bash
run: |
set -o pipefail
terraform apply -no-color -input=false -auto-approve tf.plan 2>&1 | tee apply.txt
echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
- name: Add Job Summary
if: always()
shell: bash
run: |
ref_name="${GITHUB_REF_NAME:-${GITHUB_REF#refs/*/}}"
creates="${{ needs.plan.outputs.creates }}"
updates="${{ needs.plan.outputs.updates }}"
deletes="${{ needs.plan.outputs.deletes }}"
exit_code="${{ steps.apply.outputs.exit_code }}"
creates=${creates:-0}
updates=${updates:-0}
deletes=${deletes:-0}
exit_code=${exit_code:-N/A}
echo "## 🚀 prd への Terraform 適用 (タグ)" >> $GITHUB_STEP_SUMMARY
echo "- タグ: \`${ref_name}\`" >> $GITHUB_STEP_SUMMARY
echo "- コミット: \`${GITHUB_SHA::7}\`" >> $GITHUB_STEP_SUMMARY
echo "- 実行ユーザー: @${GITHUB_ACTOR}" >> $GITHUB_STEP_SUMMARY
echo "- ディレクトリ: \`${{ env.WORKDIR_PRD }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🧮 Plan 変更サマリ" >> $GITHUB_STEP_SUMMARY
echo "- + 作成: ${creates}" >> $GITHUB_STEP_SUMMARY
echo "- ~ 更新: ${updates}" >> $GITHUB_STEP_SUMMARY
echo "- - 削除: ${deletes}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$exit_code" = "0" ]; then
echo "### ✅ Apply 成功" >> $GITHUB_STEP_SUMMARY
else
echo "### ❌ Apply 失敗" >> $GITHUB_STEP_SUMMARY
fi
echo "- 終了コード: ${exit_code}" >> $GITHUB_STEP_SUMMARY
if [ -f "${{ env.WORKDIR_PRD }}/apply.txt" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details><summary>Apply ログを表示</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
sed -n '1,300p' "${{ env.WORKDIR_PRD }}/apply.txt" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
fi
- name: Fail job if apply failed
if: ${{ steps.apply.outputs.exit_code != '0' }}
run: |
echo "Terraform Apply が終了コード ${{ steps.apply.outputs.exit_code }} で失敗しました" >&2
exit 1
GitHubActionsを作成して欲しい
v*.*.* → prdにデプロイ(plan→手動承認→apply)
Suumaryにも結果を記載して欲しい
テスト成功
問題無く成功。
Discussion