CodePipelineによるデプロイの流れでPHPUnitを走らせる際には、CodeBuildの段階でテストを実行するのがセオリーなのだと思います。
しかし諸般の事情でそういうわけにもいかず、デプロイ後にデプロイ先の環境でPHPUnitを走らせたいようなケースがあったりなかったりもすると思うんです。
どうにかしてみましょう。
具体的には、
「CodePipelineのLambda実行ステージでPHPUnitを実行」
↓
「PHPunitの実行結果に応じて、Pipelineの処理を進行/停止させる」
というお話が今回の記事となります。
PHP コード内で PHPUnit を呼び出す(実行する)という記事を以前書いたのですが、伏線回収というわけですね。
1.Lambda関数の作成
CodePipelineに設置したLambda実行ステージにて呼び出す関数を作成します。
Lambda実行ステージの設置と、Lambda関数作成自体の手順は割愛。
テスト環境と本番環境で接続先のURLを切り替えるため、CodePipelineのステージで、UserParameterとして「staging」や「production」などの文字を指定するものとします。
import boto3
import urllib.request
def lambda_handler(event, context):
codepipeline = boto3.client('codepipeline')
try:
# Get CodePipeline user param.
environment = event['CodePipeline.job']['data']['actionConfiguration']['configuration']['UserParameters']
print('environment:', environment)
# Set url
if environment == 'staging':
url = 'テスト環境の接続先URL'
elif environment == 'production':
url = '本番環境の接続先URL'
print('url:', url)
# Run PHPUnit.
response = urllib.request.urlopen(url).read().decode("utf-8")
if 'OK' in response:
# Return Success to CodePipeline.
codepipeline = boto3.client('codepipeline')
codepipeline.put_job_success_result(
jobId = event['CodePipeline.job']['id'],
executionDetails={
'summary': response,
}
)
else:
# Return Failure to CodePipeline.
codepipeline.put_job_failure_result(
jobId = event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': response
}
)
except Exception as e:
print(e)
# Return Failure to CodePipeline.
codepipeline.put_job_failure_result(
jobId = event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': str(e)
}
)
return
デフォルトのLog書き込み権限に加え、下記の権限を与えます。
{
"Effect": "Allow",
"Action": [
"codepipeline:PutJobFailureResult",
"codepipeline:PutJobSuccessResult"
],
"Resource": "*"
}
この関数では、response = urllib.request.urlopen(url).read().decode("utf-8")
の箇所でPHPUnitの実行スクリプトにアクセスし、実行結果を取得しています。
その後、PHPUnitの実行結果に応じて、Pipelineの処理を進行/停止させています。
▶正常だった場合には、Pipelineに「成功」を返して処理を進めます。
▶問題があった場合には、Pipelineに「失敗」を返して進行を止めます。
2. Lambdaから呼び出すPHPUnit実行スクリプトを用意
Lambda関数へのレスポンスは、
「Time: 231 ms, Memory: 14.00MB OK (1 test, 1 assertion)
」
などの最後の結果部分のみです。
(PHP側もLambda側も、雑に「OK」という文字の有無で成否を判定する手抜き仕様ですがご愛嬌)
実行結果の全文ログをS3に保存するため、実行関数はshell_exec()としました。
<?php
$path_to_phpunit = '/path/to/phpunit';
$path_to_tests = '/path/to/tests';
chdir($path_to_tests);
$result = shell_exec("$path_to_phpunit $path_to_tests 2>&1");
// 適宜必要な出力部分を切り出し
$sub1 = mb_substr($result, 0, mb_strpos($result, 'phpunit.xml') + 13);
$sub2 = $sub1 . mb_substr($result, mb_strpos($result, 'Time: '));
$echo_result = mb_substr($sub2, mb_strpos($sub2, 'Tests: '));
print($echo_result); // Lambdaへのレスポンス
if (strpos($echo_result, 'OK') === false) {
// phpunit実行ログの全文はS3に保存
$s3_upload_results = phpunit_log_upload_to_s3($result);
if ($s3_upload_results['result'] == 'SUCCESS') {
print($s3_upload_results['upload_name']);
} else {
// S3へのアップロードに失敗した場合、実行結果はSlack等にPOSTしてケア
...
}
}
function phpunit_log_upload_to_s3($log_body) {
// phpunit実行ログの全文をS3に保存
...
}
3. 動作確認
これらの関数は、下記のように動作します。
- 正常時
- Pipelineのステージは正常に終了
- 異常時
- 実行結果の全文ログはS3に保存
- (S3保存に失敗した場合は実行結果をSlackへPOST)
- Pipelineのステージはエラーでストップ
- 実行情報に、簡単な実行結果一文と、S3ログへのURLを記載
- 実行結果の全文ログはS3に保存
以上の仕組みにより、CodePipelineの任意のステージでPHPUnitを呼び出し、実行結果によって進行/停止させることができました。