AWS

プルリクエスト時にテスト結果をCodeCommitのアクティビティに反映したい

dai

気づけば DWS に入社して 4 ヶ月が経過していました。ダイです!
今回は、少々特殊な要件ですが 、プルリクエスト(以下「PR」)をトリガーとしてCodeBuildで実行されるテストの結果を CodeCommit のアクティビティに反映させる方法を紹介したいと思います。

実装背景

現在取り組んでいるプロジェクトでは、ソースコード管理にCodeCommitを利用しています。
そして、品質確保のため main 等特定のブランチへのマージにはPRを必須としています。
また、PR が作成された際にはソースブランチに問題がないかを確認するため CodeBuid でテストを実行しています。

PR をトリガーとしたテスト実行時の構成図


開発者がCodeCommit上でPRを作成すると、EventBridgeがそれを検知して、CodeBuild内でテストが走るようになっています。

問題点

PR とテスト結果が紐付いていないため、毎回CodeBuildのサービスページに遷移し、テスト結果を探す必要がありました。
CodeCommit 内のPRアクティビティにURLとステータスを反映させることで簡単に確認できるようにすることが今回のゴールです。

完成形のイメージ


CodeCommitのアクティビティ上で

  1. 「ビルド詳細」をクリックすると実行されたテストの詳細が確認可能
  2. ビルドバッジでテストのステータスが確認可能

変更後の構成図

変更後の構成図です。
赤枠の部分が、今回の要件のために追加したリソースとなります。

各リソースの処理内容

EventBridge

  1. トリガー
    テスト実行の際に利用するものと同じで構成とします。以下のサンプルでは、main ブランチへの PR が作成された時に実行されるように設定しています。
    ※${}の部分は自身の環境に合わせて変更してください。

サンプル

{
  "detail-type": ["CodeCommit Pull Request State Change"],
  "resources": ["arn:aws:codecommit:ap-northeast-1:${accountID}:${repositoryName}"],
  "source": ["aws.codecommit"],
  "detail": {
    "sourceReference": [{
      "anything-but": "refs/heads/main"
    }],
    "pullRequestStatus": ["Open"],
    "destinationReference": "refs/heads/main",
    "event": ["pullRequestCreated", "pullRequestSourceBranchUpdated"]
  }
}
  1. ターゲット
    次に実行する Step Functions を指定します。

Step Functions

Step Functions を間に挟んでいるのは、次のステップで作成する Lambda を遅延実行するためです。Lambdaのコード内でCodeBuildのビルド詳細を取得するため先にCodeBuildの処理が始まる必要がありますが、同一条件のEventBridgeを利用しているため順序が想定通りにならない可能性があります。
そこで、Step Functionsの待機処理を行うことにより、Lambda実行より先に確実にCodeBuildによるテストが先に始まるようにします。定義のサンプルコードは、3秒間の待機処理の後にLambda実行を開始するものです。

  1. 定義

サンプル

{
  "StartAt": "WaitSecJob",
  "States": {
    "WaitSecJob": {
      "Type": "Wait",
      "Seconds": 3,
      "Next": "LambdaJob"
    },
    "LambdaJob": {
      "End": true,
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "arn:aws:lambda:ap-northeast-1:${accountID}:function:${functionName}",
        "Payload.$": "$"
      }
    }
  },
  "TimeoutSeconds": 300
}

Lambda

順番に以下の処理を実行しています。

  1. 受け取った event から必要なパラメータを取得
  2. テスト実行に利用している CodeBuild からビルドバッチの情報とビルド詳細を取得
  3. ビルド詳細からビルドログ URL を組み立てる
  4. ビルドバッチとビルドログ URL を CodeCommit のアクティビティに反映

サンプルコード(python)

import os
import boto3

def codebuild_project_info(client):
'''
codebuild のバッジ URL とプロジェクト名を取得して返却
badge_url -> str 型のバッジ URL
project_name -> str 型のプロジェクト名
'''
codebuild_arn = os.environ.get('CODEBUILD_ARN')
batch_get_response = client.batch_get_projects(
names=[
codebuild_arn,
]
)
badge_url = batch_get_response['projects'][0]['badge']['badgerequesturl']
project_name = batch_get_response['projects'][0]['name']
return badge_url, project_name

def codebuild_build_details_arr(client, project_name):
'''
最新のビルド履歴 10 件の詳細情報を取得して返却
build_details -> dict 型のビルド情報詳細
''' # 最新のビルド履歴の ID を取得
list_build_response = client.list_builds_for_project(
projectName=project_name,
sortOrder='DESCENDING' # 最新のものから取得
)
build_ids = list_build_response['ids'][:10]

    # ソースバージョンの情報を含めた詳細情報を取得
    build_details = client.batch_get_builds(
        ids=build_ids
    )
    return build_details

def extract_build_id(builds_details, source_branch):
'''
プルリクのソースブランチとビルド ID のソースブランチを突合。
複数ソースからプルリク実行タイミングが重ならなければ基本的には 1 回目のループで一致する。
detail['id'] -> str 型のビルド ID
'''
for detail in builds_details:
source_verison = detail['sourceVersion']
if source_verison == source_branch:
return detail['id']

def lambda_handler(event, context): # 必要な情報を eventBridge から連携される event より取得
account = event['account']
region = event['region']
pr_info = event['detail']
source_reference = pr_info['sourceReference'] # e.g.refs/heads/feature/addXX
source_branch = '/'.join(source_reference.split('/')[2:]) # refs/heads 後のブランチ名
pull_request_id = pr_info['pullRequestId']
repository_name = pr_info['repositoryNames'][0]
after_commit_id = pr_info['destinationCommit']
before_commit_id = pr_info['sourceCommit']

    print(f"source repository is {repository_name}.source branch is {source_branch}")

    # codebuild関連の処理
    codebuild_client = boto3.client('codebuild')
    badge_url, project_name = codebuild_project_info(codebuild_client)
    build_details = codebuild_build_details_arr(codebuild_client, project_name)
    build_id = extract_build_id(build_details['builds'], source_branch)

    build_url = f"https://{region}.console.aws.amazon.com/codesuite/codebuild/{account}/projects/{project_name}/build/{build_id}"
    comment_template = f"[ビルド詳細]({build_url})  Build Status:![BuildBadge]({badge_url})"
    # batchurlは通常デフォルトブランチ(main)のパラメータを持っている branch=main
    # この部分を置換することでソースブランチのビルド状況を確認できるようにする
    content = comment_template.replace('main', source_branch, 1)

    # codecommitへのコメントの反映
    codecommit_clinet = boto3.client('codecommit')
    _ = codecommit_clinet.post_comment_for_pull_request(
        pullRequestId=pull_request_id,
        repositoryName=repository_name,
        beforeCommitId=before_commit_id,
        afterCommitId=after_commit_id,
        content=content
    )

codebuild_arn = os.environ.get('CODEBUILD_ARN') では、テストを実行しているCodeBuildのarnを環境変数から取得しているため、事前にコンソールなどから設定するかarnを直接埋め込むことができます。

終わりに

これらのAWSリソースを作成・デプロイするとPRをトリガーとして完成形のイメージで実現したかったことが可能になり、簡単にビルド詳細およびビルドステータスが確認できるようになり、だいぶ便利になりました!
ただ、GitHubとGithub Actionsを利用するとリソースを追加したり、コードを書いたりするまでもなくこれらの機能が準備されているので、CodeCommitも更に便利になって欲しいなと思います!

AUTHOR
dai
dai
記事URLをコピーしました