AWS StepFunctionsを使って CodeCommitでプルリク時にそのブランチ名を自動的に既存のCodeBuildの対象ブランチにしてテスト実行する。


# 動機

CodePipelineを使ってCodeCommitにpush時にCodeBuildでテストを実行できるようにはしたけれど、プルリクしてきたそのソースに対してマージ前にテストを行なってテストが通ったものをレビューしてマージしたいという思いがありました。

ビルドプロジェクトのソースを都度手で修正して行うのは実行自体は簡単ですが、ブランチ戦略によりプルリク元のブランチ名が様々である場合手動で行うとその手間が多く無駄であるということから<b>自動で既存のビルドプロジェクトの対象ブランチをプルリクをフックにそのブランチ名を使って更新できないか</b>ということを調べていていました。

lambdaによる実現の情報はあったのですが、もっとeasyに実現できないかと他の方法も考えたときに「そういえばStep Functionsというのがあったな、、、」ということから実際に行ってみました。

タイトルには「ブランチ名を」と書きましたが、同じ考えでコミットIDを指定することもできます。（そちらの方がビルドプロジェクトの実行完了は早いようです。）

## ユースケース

- コードレビューをしている。
- プルリク先はあまり変わらないが(main, develop)プルリク元のブランチ名が様々(feature_1, fotfix_xxxx)である。
- プルリクしたときにそのプルリク元のブランチ名で既存のビルドプロジェクトの対象ブランチを自動で変更したい。

## この記事の目的

タイトルにあるままですがその方法を共有します。できるだけイージーさを意識し、IaCによる作成は記載していません。。イベントの流れを参考にいただけたらと思います。

## 記事の前に

### 書いてあること

- AWS CodeBuildでのビルドプロジェクトの作成のさわり。
- ユースケースを実現するためのEvent Bridge, Step Functionsの作成、設定など。

### 書いてないこと

- 使用するサービスの詳細(CodeCommitの登録、IAMとは何か、などサービス自体の基本的な説明）
- テスト実行するアプリの詳細(Buildの仕方、Testの仕方)など。

### 免責

ほとんどかからないと思いますが、もし参考にしていただいたときAWSの使用金額はおそれいりますがご自身で管理ください。CodeBuildが少々（数円〜？）かかると思います。
また参考いただいたときに、なんらかしらの設定ミスによるセキュリティ事故に関してもご自身の責任でお願いします。

## 流れ

1. 準備　アプリ、ビルドプロジェクトの準備等
1. EventBridge, Step Functions の設定

の順で説明します。
いたずらに記事が長くなるのを避けるため補足的な説明は折り畳みにしています。

# 1.アプリ、ビルドプロジェクトの準備等


## アプリの準備とAWS CodeCommitへのpush

### 以下のようなアプリの状態を前提とします。

- コマンドでテストができるWebアプリ
- AWS CodeCommitでリモートリポジトリを登録しておく。デフォルトブランチは```main```とします。
- アプリのルートフォルダにbuildspec.ymlを用意しておく。

この記事で使用するアプリの技術は

- spring boot2
- gradle
- JUnit

を使用しています。
ただしバージョンなどはこの記事では関係なく、Javaであるか自体もこの時期には関係ないです。
テストフレームワークとしてJUnitを使用し、``` ./gradlew test``` でテストができることを確認し、
その結果の生成物をテストレポートとしてCodeBuildに読ませるので以下のように記述しておきます。


```buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - ./gradlew test
reports:
  junit-reports:
    files:
      - "build/test-results/**/*.xml"
    file-format: "JUNITXML"
```
[参考:ステップ 2: buildspec ファイルを作成する](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/getting-started-cli-create-build-spec.html)
[参考:テストレポートの作成](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/report-create.html)



buildspec.ymlをルートに入れた状態でコミット,pushしておきます。

### テストを行うCodeBuildのプロジェクトの作成

テストが実行できるビルドプロジェクトを作っておきます。プロジェクト名は任意です。
ここでは
プロジェクト名 TestProject
リポジトリ spring-demo
デフォルトブランチ名 main
としました。

<details><summary>作成の例</summary><div>
ビルドプロジェクトも動的に作成することはできますが、今回は使い回してソースブランチを変えることを行うので
手で作っておきます。

ビルドプロジェクトから
![buildproject.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/7e2a64fe-2571-5eb5-8490-ee3f00d5d7df.png)
作成を押下

![select_create_project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/a234010d-d69b-3626-25dd-8367d1a03b88.png)
プロジェクトの名前は```TestProject```とします。
![makeBuildProject.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/fe884d3b-3b48-08c4-fedc-8c1317931bd9.png)

今回リポジトリは```spring-demo```という名前で設定しました(任意）。そのリポジトリを選択し、
ブランチをまず```main```にしておきます。

![source.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/5c54662b-5412-bef9-b7c2-189e7350fc74.png)

ビルドに使うイメージはAWSのマネージドを使います。使用するアプリのテストコマンドがマネージドイメージではできない場合はカスタムDockerイメージ作り指定しますがそのまま```./gradlew test```も使用できるためマネージドイメージを使用します。

サービスロールは新しいサービスロースを選択します。
![ap-northeast-1.console.aws.amazon.com_codesuite_codebuild_project_new_region=ap-northeast-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/cbe40af0-b4ad-febb-64e1-a009f29990a7.png)

サービスロールは自分で作ることもできますが、AWS CLIを使います。
[参考:IAMロールとAWSサービスロールの違いって何？（自分なりの理解）](https://qiita.com/yuta-katayama-23/items/0606e8d590968e43fd27)

今回はテストだけでビルド生成物は使用しませんので「アーティファクトなし」にしておきます。
![buildspec.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/ebf317c7-c536-8c78-eb74-00a08e3588b8.png)

ログは使用するのでチェックしておきます。グループ名等は空のままにします。（自動で生成されるものを使います。）
![click_make_project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/ef20a539-beee-7823-2556-614ff4b9c2a6.png)

</div></details>

作成後、ビルドの実行を行い成功を確認します。当然この時点では　「ソースのバージョン」が ```main```であることを確認しておきます。
![ビルドの実行.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/c13037d8-d214-98d5-0a1c-6acd18c3f24b.png)

ビルド履歴 > レポート にてテスト結果が確認できます。（最低限のデモのため１件だけです。）
![テスト結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/d1e9ff38-8ac5-67b7-0c58-e6f2874c8241.png)


# 2. EventBridge, Step Functions の設定

feature_1というブランチを作って以下のようにbuildspec.ymlを変更してコミット、プッシュします。(この変更に特に意味はありません。mainとの差分をつくるためです。)


```diff_yaml
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の作成

![ステートマシンを作る.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/f473cd85-ae42-ea97-f6d5-58fe76be51b5.png)

「ワークフローを視覚的に設計」を選びます。「サンプルプロジェクトを実行」にビルドスタートからSNS通知までのサンプルがあるのですが、その前段階を行いたいので一からつくります。
![視覚的に設計.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/1dd94d5b-6e75-831a-80a7-37cd85df2b85.png)

左上の検索枠からCodeBuildを検索し「UpdateProject」「codebuild StartBuild」を選んで以下のように配置します。
![updateProject.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/7cac5295-bb01-b933-411c-eec475e72b35.png)

UpdateProjectをクリックし、右側にあるAPIパラメータに以下のように記述します。
![updateProjectAPI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/2da061ce-0aea-8295-fcb5-fd495a50bb50.png)

```json
{
   "Name": "TestProject",
   "SourceVersion.$": "$.detail.sourceReference"
}
```

このパラメータを使うのがポイントです。パラメータを使う、ということ自体は簡単なことなんですが知らないといつまでも「その使い方があったのか」的なアレ?です。

<details><summary>パラメータについて</summary><div>

ここで```$.detail.sourceReference```とはなんでしょうか？
まだEvent Bridgeは設定していませんが、プルリクエストを行った時のイベントは以下のように入力されます。
![pullRequestEvent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/50f08ede-069d-ee9f-9fbd-3d11ec59ae64.png)
[参考:Step Functions 実行ステータス変更用 EventBridge (CloudWatch Events)](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/cw-events.html)
[参考:pullRequestCreated イベント](https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/monitoring-events.html#pullRequestCreated)

ここにある```detail.sourceReference```をパラメータで参照する時の書き方が```$detail.sourceReference```となります。
ここですでに予想ができるかもしれませんが、プルリクエストしたときのそのブランチがここで展開されます。
```SourceVersion.$```はCodeBuild APIの```update-project```のキーの一部です。
Step Functionsではキーはパスカルケースにする必要があるため先頭は大文字です。
[参考:update-project](https://docs.aws.amazon.com/cli/latest/reference/codebuild/update-project.html)
[参考:InputPath、パラメータ、および ResultSelector](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/input-output-inputpath-params.html)
変数（パラメータ）を使用する時キーは```~.$```、バリューは```$.~```とする必要があります。

流れるイベントがどのようなJSONデータになるかは実際はドキュメントを見て確認するより、
イベントを流したり、Step Functionsのデータフローシュミレーターで試したりした方がイメージつかみやすいと思います。


</div></details>

同様にStartProjectをクリックし右側にあるAPIパラメータに以下のように記述します。```ProjectName.$```はstart-build　APIに対するキーになります。
```$.Project.Name```はUpdateProjectのステップのoutputとして出力されるものを参照しています。

```json
{
  "ProjectName.$": "$.Project.Name"
}
```
[参考:start-build](https://docs.aws.amazon.com/cli/latest/reference/codebuild/start-build.html#start-build)

右上の「次へ」

![PullRequestStateMachineを修正.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/393d95da-dff6-7f76-8239-ded85724fa92.png)

「次へ」
「ステートマシンを編集」

ここではステートマシン名を```PullRequestStateMachine```としました。（任意）
アクセス許可は「新しいロールの作成」
一番下にUpdateProjectのActionが足りない警告がでますがここでは
「ステートマシンの作成」を押下し、まず作ってしまいます。

遷移後ロールにポリシーを追加します。

<details><summary>ポリシーの追加</summary><div>

右上の「IAMロールを編集」を押下します。
![IAMロールを編集.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/1ce8f66a-3da9-9bf1-0c9a-1132cec666e5.png)

![ポリシー名.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/09b61752-3b45-30fc-93f5-e113e4f35db6.png)

ActionにUpdatePolicyを追加して保存します。

![policy編集.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/e9db9b81-b5eb-eb9f-2732-35a5a2f6e137.png)

ポリシーの確認 > 変更の保存

</div>
</details>

これでプルリクエストしたブランチを対象ブランチにしてテストを実行するStateMachineができました。
ただし後述しますがこのままではプルリクエストをクローズしたとき、マージしたときも実行されてしまうのでのちに修正します。

### Event Bridgeの作成

Event Bridge からpullRequestイベントを送信する新しいルールを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/ee23b2a0-0577-445f-bc9c-3b80f7b38ae1.png)

プルリクエストをしたイベントタイプは```CodeCommit Pull Request State Change```にします。これはプルリクエストの作成、そのプルリクエストのブランチが更新されたとき、クローズの時、マージしたときを補足します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/86e7b0b4-e65b-9f55-64d3-641aa1aed5b3.png)

ターゲットを先ほど作ったステートマシンにします。
ロールはここでは新しいロールにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/e1be066e-5287-0d4a-cbcc-2b80b981ed3f.png)

作成を押下します。

これで流れができたので試してみます。

CodeCommitでプルリクエストを作成します。

![プルリクエストの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/427f0edc-cd31-96ec-b133-f712d2f2b8d0.png)

実行が確認できました。

![マシーン実行.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/88c121a5-b702-0a5c-3ab4-a9520af8f4b6.png)

CodeBuildのログを見るとプルリクしたブランチに変わっているのがわかります。プルリクから自動でブランチ名を変えた実行ができました。

![移り変わり.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/120c2660-4711-0131-99b2-e72382546025.png)

### クローズした時の対応

このままではクローズしたとき、マージしたときもUpdateProjectが実行されるのでStepFunctionsを編集します。
ChoiceとPassを追加します。
![choiceとpass.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/07eb00ad-5e4f-0f42-e9d9-52f1b94c478c.png)

Choiceをクリックし右側のRule > Edit にて以下のようにします。

![Condition.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/b7d1ccb6-a4a3-74db-fd2a-fc03e7719408.png)

プルダウンを Simple
variableを$.detail.pullRequestStatus
Operatorをis equal to
valueをString constant
値を Closed
保存します。

これでプルリクの状態変更が```Close```の時Passに行くようになります。
Passは特に編集しません。
Passを追加したのはChoiceから直接ENDにできなかったためです。

<details><summary>この時点のステートマシンの定義</summary><div>

```json

{
  "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
    }
  }
}

```


</div></details>

これでプルリクエストをクローズ、またはマージすると以下のようになります。
![WhenCLosed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/cacfcf70-6031-ecd3-816a-ac980e3b7100.png)
プルリク作成時は以下になります。
![WhenOpen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/ce55fc5d-b826-f30a-56c5-5d72fe91e885.png)

これでプルリクエストから動的に既存のビルドプロジェクトの対象ブランチを変更することができました。

## このあとは

ここから成功、失敗をSNSトピックに送信してチャットツールに連携するなど、、、。

# やってみて

何かのイベントをフックに何かする　== lambdaという先入観がありましたがAWSサービス間のつなぎだと
Step Functionsを使うのもありだなと思いました。
あらためてAWSがあらゆるサービスでAPIを公開していることによるサービス間の連携の強力さ、素晴らしさを感じました。