💯
AWSCodePipeline 手動承認ステージを常に最新化するLambda関数をつくってみた
概要
-
以前の記事でAWS CodePipelineの多重実行の挙動をまとめましたが、多重実行した際の古い実行をデプロイさせない為の構成を紹介します。
-
想定しているのは以下のようなCodePipelineの構成です。
- 実行モード:SUPERSEDED
ステージ名 サービス Source CodeCommit Build CodeBuild Approval 手動承認 <--承認せずに後発のパイプラインを実行 Deploy CodeDeploy -
手動承認ステージが保留中に新しいパイプラインを実行した場合、SUPERSEDEDモードではインバウンド実行が発生してしまいます。
つくりたいもの
- 手動承認ステージの前にLambda関数を実行するステージを作成します。
- Lambda関数では手動承認ステージのステータスと実行IDを取得し、ステータスによってステージを終了させます。
手動承認ステージのステータス | 処理 |
---|---|
InProgress(進行中) | 進行中の手動承認ステージを終了させる。 |
Cancelled(キャンセル) ※ステージの実行中にパイプラインの定義を更新 |
キャンセルされた手動承認ステージを終了させる。 |
Stopped(手動停止)Stopping(手動停止中) ※停止して待機を実行 |
停止された承認ステージを終了させる。 |
Abandoned(終了) | インバウンド実行が発生しないので無視。 |
Succeeded(成功) | インバウンド実行が発生しないので無視。 |
Failed(失敗) | インバウンド実行が発生しないので無視。 |
- SUPERSEDEDモードでは各ステージ1つ実行しかできず、後発の実行がインバウンド実行となってしまいます。
- 2つ以上の進行中の実行がある場合に古い実行を終了させる処理を実装します。
つくってみた
1. Lambda用のIAMロールの作成
- ロール名:mori-role-lambda-codepipeline-stage
- 信頼されたエンティティタイプ:AWSのサービス
- ユースケース:Lambda
- 許可ポリシー:mori-policy-lambda-codepipeline-stage
mori-policy-lambda-codepipeline-stage
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:${リージョン}:${アカウントID}:log-group:${ロググループ名}"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:${リージョン}:${アカウントID}:log-group:${ロググループ名}:*"
}
{
"Effect": "Allow",
"Action": [
"codepipeline:GetPipelineState",
"codepipeline:StopPipelineExecution"
],
"Resource": "${パイプラインARN}"
},
{
"Effect": "Allow",
"Action": "codepipeline:PutJobSuccessResult",
"Resource": "*"
}
]
}
ポリシーについて
-
codepipeline:GetPipelineState
:指定したパイプラインの状態を取得。 -
codepipeline:StopPipelineExecution
:指定したパイプラインの実行を停止する。 -
codepipeline:PutJobSuccessResult
:ジョブの成功をレポートする。Resourceに*のみサポート。
2. Lambda関数の作成
- Lambda関数を作成します。
- 関数名:mori-lambda-codepipeline-stage
- ランタイム:Python 3.12
- アーキテクチャ:x86_64
- 実行ロール:既存のロールを使用する
- 既存のロール:mori-role-lambda-codepipeline-stage ※手順1で作成したロール
- コードを作成します。
lambda_fuction.py
import json
import boto3
cp = boto3.client('codepipeline')
pipeline_name = '${パイプライン名}'
def lambda_handler(event, context):
# 手動承認ステージの実行ID、ステータスを取得
approval_stg_state = cp.get_pipeline_state(name = pipeline_name)['stageStates'][3]['latestExecution']
# 手動承認ステージが実行されているID
approval_stg_id = approval_stg_state['pipelineExecutionId']
# 手動承認ステージの実行ステータス
approval_stg_status = approval_stg_state['status']
# 手動承認ステージが終了していなければ終了させる
if approval_stg_status in ["InProgress", "Cancelled", "Stopped", "Stopping"]:
stopped_exec_id = cp.stop_pipeline_execution(
pipelineName = pipeline_name,
pipelineExecutionId = approval_stg_id,
abandon = True,
reason = 'New Pipeline has Started'
)
# Lambdaステージを終了する
cp.put_job_success_result(jobId = event['CodePipeline.job']['id'])
return
コードについて
approval_stg_state = cp.get_pipeline_state(name = pipeline_name)['stageStates'][3]['latestExecution']
-
10行目の
get_pipeline_state()
でパイプラインの状態を取得しています。 -
[3]
となっているのはレスポンスのstageStates{}
の3番目の配列に手動承認ステージの情報が格納されているからです。ステージ名 サービス stageStates{}
内の配列Source CodeCommit [0] Build CodeBuild [1] Lambda Lambda [2] Approval 手動承認 [3] <--実行IDとステータスを取得したい Deploy CodeDeploy [4]
# Lambdaステージを終了する
cp.put_job_success_result(
jobId = event['CodePipeline.job']['id']
)
- 28行目の
cp.put_job_success_result()
でLambda関数を成功として終了させています。 - この処理が無ければLambda関数を組み込んだステージが終了できません。
-
event['CodePipeline.job']['id']
でCodePipelineのeventから渡される実行IDを指定しています。
3. CodePipelineにステージを追加
- CodePipelineを編集して、手動承認ステージの前にLambda関数のステージを追加します。
- アクション名:Lambda
- アクションプロバイダー:AWS Lambda
- 関数名:mori-lambda-codepipeline-stage ※手順2で作成した関数
4. Lambdaステージの検証
- 手動承認ステージが保留中に後発のパイプラインを実行します。
- 後発のパイプラインの実行がLambdaステージに進行後、手動承認ステージで進行中の実行を終了させます。
- 最新のパイプライン実行が手動承認ステージに進行しました。
インバウンド実行が発生しません!
おわりに
- 古い実行のデプロイを防止する手軽かつ確実な方法かなと思います。
- 作業手順にありそうな「最新の実行である事を確認後に承認する事」といった、人的ミス発生要因は低減できそうです。
参考
Discussion