CircleCIのOnly build pull requestsをoffにしてもプルリクエストを作ったらJobを実行したい

この記事は CircleCI Advent Calendar 2018 2日目の記事です。
前回は @miyajan さんの [CircleCI Orbs 入門](https://www.kaizenprogrammer.com/entry/2018/12/01/111145) でした。

# TL;DR

- CircleCI の **Only build pull requests** を `off` にしていると、プルリクエストを作った時に動いて欲しい CircleCI の job を設定できない
- そこで GitHub の webhook を利用して、プリリクエストが作られたら CircleCI の Job を実行する **serverless アプリ** を作って設定した
- CircleCI の **Only build pull requests を `off` にした理由** と、**作った serverless アプリと設定方法** ついて説明する


# Only build pull requests とは

CircleCI には **Only build pull requests** という設定があります。
名前の通り、プルリクエストが作らられている時だけ CircleCI のビルドが実行されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/0c39f92d-b424-9d2c-ba21-ae76b28511f4.png)

この設定を `on` にしておけば、無駄にビルドして CircleCI のキューを圧迫することもなくて良いのですが、私は次の理由で `off` に設定しています。

# CircleCI の Only build pull requests を `off` にする理由

## `master` ブランチのデプロイを自動化したい

`master` ブランチが更新されたらデプロイしたいのですが、`master` ブランチからプルリクエストを作ることはないので、 `Only build pull requests` が `on` になっていると CicleCI のビルドが実行されません。

## さっさとテストを実行しておきたい

CircleCI で UT を実行していますが、これが少々時間がかかってしまいます。
なので push したらさっさとテストを実行して、プルリクエストを作る頃にはある程度終わってくれている方が嬉しいです。

# Only build pull requests を `off` にする弊害

では Only build pull requests を `off` にすると万事解決かというと、そうもいきません。
私は次の問題に直面しました。

## キューが詰まる

push する度にジョブが実行されるのですから、当然、ビルドのキューが増えて詰まります。
これはお金の力で解決しました :moneybag: 

## reviewdog が働かない :dog: 

[reviewdog](https://github.com/haya14busa/reviewdog) というツールがあります。
これは、 `rubocop` や `golint` などの静的解析ツールの結果を、GitHubのプルリクエストにコードへのコメントとして登録してくれるツールです。
私はこのツールを使って機械的なコードレビューを自動化しています。↓のような感じです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/121bd0af-88b4-6dcc-385b-ae782ea1fae9.png)


ただ、GitHubの **プルリクエスト** にコメントを登録するツールですから、プルリクエストがなければ機能しません。
しかし Only build pull requests を off にしていると、プルリクエストを作る前に CircleCI のビルドが実行されますから reviewdog を実行するとエラーになってしまいます。

対策として GitHub のプルリクエストが作られたら CircleCI のジョブをAPI経由で実行する仕組みを作ることにしました。

# プルリクエストが作られたら reviewdog だけ実行する serverless アプリケーション

というわけで、プルリクエストが作られたら reviewdog だけを実行する仕組みを [serverless](https://serverless.com/) で作りました。
↓の図の黒い部分です。

<img src="https://qiita-image-store.s3.amazonaws.com/0/27201/54f58090-d0d4-7387-ede9-90e7de438ace.png" width="50%">

## 作った `serverless` アプリの説明

https://github.com/yasuhiroki/serverless-gh-hook-circle-job に公開しています。
AWS を使う前提の serverless です。

### CircleCI の API トークンを設定

また、CircleCIのジョブをAPI経由で実行するにはトークンが必要なので取得します。
トークンは User setting -> Personal API tokens から取得できます。

取得したら `serverless.yml` に記述して deploy します。

```yaml
functions:
  lint:
    handler: index.handler
    description: hook circleci job by web hook of github pull-request
    events:
      - http:
          path: circleci
          method: post
    environment:
      CIRCLECI_TOKEN: # ここに取得したトークンを書く
      TARGET_JOB: eslint
      TARGET_REPO: yasuhiroki/serverless-gh-hook-circleci-job
      IGNORE_BRANCHES: master
```

生トークンを書くことにリスクがあるのであれば AWS KMS を使うと良いですね。
今回の実装では使っていませんが、いずれ対応したいと思います。 [^1]

[^1]: 自社ではKMSを使った実装にしていますが、ハードコーディングされてるので公開できず...

### API Gateway Endpoint を GitHub の webhook に登録

作った API Gateway の URL を GitHub の webhook に登録します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/27201/d380dafa-6608-39cd-dad3-0cafde35551f.png" width="50%">

プルリクエストのイベントだけ Hook されれば良いので、そのように設定しておきましょう。

<img src="https://qiita-image-store.s3.amazonaws.com/0/27201/04593e6e-d956-c3bc-a953-b1a22eada186.png" width="40%"> <img src="https://qiita-image-store.s3.amazonaws.com/0/27201/40aeed35-977f-80d0-76a2-a0df8d2948f4.png" width="40%">

### CircleCI の config.yml を設定

reviewdog は プルリクエストが作られた時にだけ実行してほしいので config.yml を調整します。
プルリクエストが作られていれば環境変数 `CI_PULL_REQUEST` に値が入るのでチェックすれば良いですね。

```yaml
serverless_golang_lint:
  steps:
    - run:
        name: Run golint and reviewdog
        command: |
          test ${CI_PULL_REQUEST} || exit 0 # プルリクエストが作られていない時は skip
          golint ./... |  tee golint.txt || true
          cat golint.txt | reviewdog -f=golint -reporter=github-pr-review
```

# まとめ

CircleCI の **Only build pull requests** を `off` にした状態で、プルリクエストが作られた時に CircleCI のジョブを実行する方法を serverless で実現してみました。

この方法以外にも、プルリクエストを作った後に適当なcommitをpushして CircleCI のビルドを走らせる方法でも良いかもしれませんね。

CircleCI Advent Calendar の明日の記事は @tomonorimatsumura さんです :+1: