動機
CodePipelineを使ってCodeCommitにpush時にCodeBuildでテストを実行できるようにはしたけれど、プルリクしてきたそのソースに対してマージ前にテストを行なってテストが通ったものをレビューしてマージしたいという思いがありました。
ビルドプロジェクトのソースを都度手で修正して行うのは実行自体は簡単ですが、ブランチ戦略によりプルリク元のブランチ名が様々である場合手動で行うとその手間が多く無駄であるということから自動で既存のビルドプロジェクトの対象ブランチをプルリクをフックにそのブランチ名を使って更新できないかということを調べていていました。
lambdaによる実現の情報はあったのですが、もっとeasyに実現できないかと他の方法も考えたときに「そういえばStep Functionsというのがあったな、、、」ということから実際に行ってみました。
タイトルには「ブランチ名を」と書きましたが、同じ考えでコミットIDを指定することもできます。(そちらの方がビルドプロジェクトの実行完了は早いようです。)
ユースケース
- コードレビューをしている。
- プルリク先はあまり変わらないが(main, develop)プルリク元のブランチ名が様々(feature_1, fotfix_xxxx)である。
- プルリクしたときにそのプルリク元のブランチ名で既存のビルドプロジェクトの対象ブランチを自動で変更したい。
この記事の目的
タイトルにあるままですがその方法を共有します。できるだけイージーさを意識し、IaCによる作成は記載していません。。イベントの流れを参考にいただけたらと思います。
記事の前に
書いてあること
- AWS CodeBuildでのビルドプロジェクトの作成のさわり。
- ユースケースを実現するためのEvent Bridge, Step Functionsの作成、設定など。
書いてないこと
- 使用するサービスの詳細(CodeCommitの登録、IAMとは何か、などサービス自体の基本的な説明)
- テスト実行するアプリの詳細(Buildの仕方、Testの仕方)など。
免責
ほとんどかからないと思いますが、もし参考にしていただいたときAWSの使用金額はおそれいりますがご自身で管理ください。CodeBuildが少々(数円〜?)かかると思います。
また参考いただいたときに、なんらかしらの設定ミスによるセキュリティ事故に関してもご自身の責任でお願いします。
流れ
- 準備 アプリ、ビルドプロジェクトの準備等
- EventBridge, Step Functions の設定
の順で説明します。
いたずらに記事が長くなるのを避けるため補足的な説明は折り畳みにしています。
1.アプリ、ビルドプロジェクトの準備等
アプリの準備とAWS CodeCommitへのpush
以下のようなアプリの状態を前提とします。
- コマンドでテストができるWebアプリ
- AWS CodeCommitでリモートリポジトリを登録しておく。デフォルトブランチはmainとします。
- アプリのルートフォルダにbuildspec.ymlを用意しておく。
この記事で使用するアプリの技術は
- spring boot2
- gradle
- JUnit
を使用しています。
ただしバージョンなどはこの記事では関係なく、Javaであるか自体もこの時期には関係ないです。
テストフレームワークとしてJUnitを使用し、 ./gradlew test でテストができることを確認し、
その結果の生成物をテストレポートとしてCodeBuildに読ませるので以下のように記述しておきます。
version: 0.2
phases:
  build:
    commands:
      - ./gradlew test
reports:
  junit-reports:
    files:
      - "build/test-results/**/*.xml"
    file-format: "JUNITXML"
参考:ステップ 2: buildspec ファイルを作成する
参考:テストレポートの作成
buildspec.ymlをルートに入れた状態でコミット,pushしておきます。
テストを行うCodeBuildのプロジェクトの作成
テストが実行できるビルドプロジェクトを作っておきます。プロジェクト名は任意です。
ここでは
プロジェクト名 TestProject
リポジトリ spring-demo
デフォルトブランチ名 main
としました。
作成の例
今回リポジトリはspring-demoという名前で設定しました(任意)。そのリポジトリを選択し、
ブランチをまずmainにしておきます。
ビルドに使うイメージはAWSのマネージドを使います。使用するアプリのテストコマンドがマネージドイメージではできない場合はカスタムDockerイメージ作り指定しますがそのまま./gradlew testも使用できるためマネージドイメージを使用します。
サービスロールは自分で作ることもできますが、AWS CLIを使います。
参考:IAMロールとAWSサービスロールの違いって何?(自分なりの理解)
作成後、ビルドの実行を行い成功を確認します。当然この時点では 「ソースのバージョン」が mainであることを確認しておきます。

ビルド履歴 > レポート にてテスト結果が確認できます。(最低限のデモのため1件だけです。)

2. EventBridge, Step Functions の設定
feature_1というブランチを作って以下のようにbuildspec.ymlを変更してコミット、プッシュします。(この変更に特に意味はありません。mainとの差分をつくるためです。)
version: 0.2
phases:
  build:
    commands:
+      - echo 'feature_1'
      - ./gradlew test
reports:
  junit-reports:
    files:
      - "build/test-results/**/*.xml"
    file-format: "JUNITXML"
ここでfeature_1からmainにプルリクを作ったとき、そのイベントをフックに
- 自動で先ほど作ったビルドプロジェクトのソースブランチがfeature_1になる。
- その後自動でテストを実行する
の流れを行うのはどのようにすればよいでしょうか。方法はいろいろあると思いますがStep Functionsを使います。
Step Functionsの作成
「ワークフローを視覚的に設計」を選びます。「サンプルプロジェクトを実行」にビルドスタートからSNS通知までのサンプルがあるのですが、その前段階を行いたいので一からつくります。

左上の検索枠からCodeBuildを検索し「UpdateProject」「codebuild StartBuild」を選んで以下のように配置します。

UpdateProjectをクリックし、右側にあるAPIパラメータに以下のように記述します。

{
   "Name": "TestProject",
   "SourceVersion.$": "$.detail.sourceReference"
}
このパラメータを使うのがポイントです。パラメータを使う、ということ自体は簡単なことなんですが知らないといつまでも「その使い方があったのか」的なアレ?です。
パラメータについて
ここで$.detail.sourceReferenceとはなんでしょうか?
まだEvent Bridgeは設定していませんが、プルリクエストを行った時のイベントは以下のように入力されます。

参考:Step Functions 実行ステータス変更用 EventBridge (CloudWatch Events)
参考:pullRequestCreated イベント
ここにあるdetail.sourceReferenceをパラメータで参照する時の書き方が$detail.sourceReferenceとなります。
ここですでに予想ができるかもしれませんが、プルリクエストしたときのそのブランチがここで展開されます。
SourceVersion.$はCodeBuild APIのupdate-projectのキーの一部です。
Step Functionsではキーはパスカルケースにする必要があるため先頭は大文字です。
参考:update-project
参考:InputPath、パラメータ、および ResultSelector
変数(パラメータ)を使用する時キーは~.$、バリューは$.~とする必要があります。
流れるイベントがどのようなJSONデータになるかは実際はドキュメントを見て確認するより、
イベントを流したり、Step Functionsのデータフローシュミレーターで試したりした方がイメージつかみやすいと思います。
同様にStartProjectをクリックし右側にあるAPIパラメータに以下のように記述します。ProjectName.$はstart-build APIに対するキーになります。
$.Project.NameはUpdateProjectのステップのoutputとして出力されるものを参照しています。
{
  "ProjectName.$": "$.Project.Name"
}
右上の「次へ」
「次へ」
「ステートマシンを編集」
ここではステートマシン名をPullRequestStateMachineとしました。(任意)
アクセス許可は「新しいロールの作成」
一番下にUpdateProjectのActionが足りない警告がでますがここでは
「ステートマシンの作成」を押下し、まず作ってしまいます。
遷移後ロールにポリシーを追加します。
これでプルリクエストしたブランチを対象ブランチにしてテストを実行するStateMachineができました。
ただし後述しますがこのままではプルリクエストをクローズしたとき、マージしたときも実行されてしまうのでのちに修正します。
Event Bridgeの作成
Event Bridge からpullRequestイベントを送信する新しいルールを作ります。
プルリクエストをしたイベントタイプはCodeCommit Pull Request State Changeにします。これはプルリクエストの作成、そのプルリクエストのブランチが更新されたとき、クローズの時、マージしたときを補足します。
ターゲットを先ほど作ったステートマシンにします。
ロールはここでは新しいロールにします。

作成を押下します。
これで流れができたので試してみます。
CodeCommitでプルリクエストを作成します。
実行が確認できました。
CodeBuildのログを見るとプルリクしたブランチに変わっているのがわかります。プルリクから自動でブランチ名を変えた実行ができました。
クローズした時の対応
このままではクローズしたとき、マージしたときもUpdateProjectが実行されるのでStepFunctionsを編集します。
ChoiceとPassを追加します。

Choiceをクリックし右側のRule > Edit にて以下のようにします。
プルダウンを Simple
variableを$.detail.pullRequestStatus
Operatorをis equal to
valueをString constant
値を Closed
保存します。
これでプルリクの状態変更がCloseの時Passに行くようになります。
Passは特に編集しません。
Passを追加したのはChoiceから直接ENDにできなかったためです。
この時点のステートマシンの定義
{
  "Comment": "A description of my state machine",
  "StartAt": "Choice",
  "States": {
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.detail.pullRequestStatus",
          "StringEquals": "Closed",
          "Next": "Pass"
        }
      ],
      "Default": "UpdateProject"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    },
    "UpdateProject": {
      "Type": "Task",
      "Parameters": {
        "Name": "TestProject",
        "SourceVersion.$": "$.detail.sourceReference"
      },
      "Resource": "arn:aws:states:::aws-sdk:codebuild:updateProject",
      "Next": "CodeBuild StartBuild"
    },
    "CodeBuild StartBuild": {
      "Type": "Task",
      "Resource": "arn:aws:states:::codebuild:startBuild",
      "Parameters": {
        "ProjectName.$": "$.Project.Name"
      },
      "End": true
    }
  }
}
これでプルリクエストをクローズ、またはマージすると以下のようになります。

プルリク作成時は以下になります。

これでプルリクエストから動的に既存のビルドプロジェクトの対象ブランチを変更することができました。
このあとは
ここから成功、失敗をSNSトピックに送信してチャットツールに連携するなど、、、。
やってみて
何かのイベントをフックに何かする == lambdaという先入観がありましたがAWSサービス間のつなぎだと
Step Functionsを使うのもありだなと思いました。
あらためてAWSがあらゆるサービスでAPIを公開していることによるサービス間の連携の強力さ、素晴らしさを感じました。


















