TECHSTEP

ITインフラ関連の記事を公開してます。

【メモ】CodeCommit上でPR作成時に変更のあったファイルに合わせてCodeBuildの処理を変更する

背景

CI/CDを実現するために各種ソフトウェアを設定するなかで、変更のあったファイルに合わせて起動するジョブを変更したいときがあります。

例えばファイルによって適用するジョブを変更したい場合です。処理対象のファイルによって必要なコマンドライン引数が異なったり、ファイルごとに処理することでCI完了までの時間を短くしたいなどの理由から、変更するファイルによって起動するジョブを変えたいときがあります。

今回はAWS CodeCommit/CodeBuildを使った状況で、上記のようなファイルごとに処理を分ける方法を検証しました。

方法

CodeBuildでファイルごとに処理を分けるため、Pull Requestから変更のあったファイルを特定する対象のファイルを指定してCodeBuildのビルドを実行する、という2段階で処理することを考えました。

Pull Requestから変更のあったファイルを特定するには、例えば git diff コマンドを使う方法もあります。今回はAWSのサービスを使っているので、CodeCommitの GetDifferences というAPIを使うことにしました。このAPIは2つのCommit識別子 (ブランチ、タグ、Commit IDなど) を比較して、Responseとして差分を返します。この中には差分のあったファイルパスが含まれているので、これを後段の処理に使います。

また対象のファイルを指定してCodeBuildのビルドを実行するために、CodeBuildに環境変数を設定し、これを取得したファイルパスを上書きすることで実現しました。CodeBuildの StartBuild APIenvironmentVariablesOverride というパラメータで環境変数を上書きすることができます。ここに取得したファイルパスを指定し、CodeBuildの処理の中でこれを指定する形で処理しました。

確認

今回の動作確認のために以下のリソースを使用します。

  • Amazon EventBridge: 前回と同じイベントパターンを使用します。
  • AWS CodeBuild: main ブランチをターゲットに、TARGET_PATH環境変数に設定しました。
  • AWS CodeCommit: テスト用の2ファイルと buildspec.yml を用意しました。
  • AWS Lambda: IAMロールにCodeCommitの codecommit:GetDifferences 権限およびCodeBuildの StartBuild 権限を付与します。

またLambda関数および buildspec.yml は以下の通りです。

Lambda関数

import boto3

codecommit = boto3.client('codecommit')
codebuild = boto3.client('codebuild')

# 実行対象のCodeBuildプロジェクト名を指定
project = "test-codebuild-20230430"

def lambda_handler(event, context):
    response = codecommit.get_differences(
        repositoryName=event['detail']['repositoryNames'][0],
        beforeCommitSpecifier=event['detail']['destinationCommit'],
        afterCommitSpecifier=event['detail']['sourceCommit']
    )

    target_list = [difference['afterBlob']['path'] for difference in response['differences']]

    for target in target_list:
        build_response = start_codebuild(project, event['detail']['sourceCommit'], target)
        print('%s for %s is %s at %s' % (build_response['build']['projectName'], target, 
            build_response['build']['buildStatus'], build_response['build']['sourceVersion']))
    
    return {}
    
def start_codebuild(ProjectName, CommitID, target_path):
    response = codebuild.start_build(
        projectName=ProjectName,
        sourceVersion=CommitID,
        environmentVariablesOverride=[
            {
                'name': 'TARGET_PATH',
                'value': target_path,
                'type': 'PLAINTEXT'
            },
        ],
    )
    return response

buildspec.yml

version: 0.2

phases:
  build: 
    commands:
      - echo Check target file name
      - echo $TARGET_PATH
      - echo Print target file
      - cat $TARGET_PATH

まず1つのファイルに変更のあった場合を見るため、以下のPull Requestを作成します。ここでは test01.txt に文字列を追加します。

上記Pull Requestの作成をトリガーに、EventBridgeからLambda関数を実行します。Lambda関数の中で変更のあったファイルを取得して TARGET_PATH を設定し、CodeBuildのビルドを開始します。CodeBuildの実行結果を見ると、Pull Requiestで変更のあった test01.txt の内容だけが出力されます。

次に複数のファイルに変更があった場合を見るため、以下のPull Requestを作成します。

Pull Request作成後にCodeBuildの管理画面を見ると、以下のような2つのビルドが実行完了したことを確認できます。

それぞれの結果を見ると、 test01.txt test02.txt それぞれの内容が個別に出力されていることが確認できます。

test01.txt

test02.txt

最後にCodeBuildに記載されるCommit IDを見ると、Pull Request作成時のCommit IDが使われていることが確認できます。

参考