Pull Request時のみCircleCIを実行する


# やりたかったこと
GitHubのPull Request時にGoプロジェクトのビルド前チェックを行いたかったのでCircleCIで実現しました。
要件としては下記です。

- errcheckやらlint等のチェックを実施
- migrationを実施
    - DB処理を行うため、MySQLとRedisのイメージも利用
- 処理時間の最大限短縮
    - 必要な外部ライブラリを取得済みのカスタムイメージを利用（イメージはプライベートリポジトリに保持）
    - vendorはキャッシュを利用
- 処理結果をslackに通知

# 方針策定

migrationの確認はDBを立ち上げなくてはならず、なおかつDBの起動を待って処理を行わなくてはなりません。そのため、dockerizeコマンドをstepsに組み込みます。
今回は例としてMySQLとRedisのDockerイメージを使用しています。

また、チェック処理やmigrationには外部ライブラリの利用が必要となり、それらのインストールに時間がかかってしまいます。
処理時間の短縮を図るため、ライブラリを予めインストールしたカスタムイメージの利用とvendorのキャッシュ化を行います。

# 設定手順

GitHubをCircleCI連携するためにはプロジェクト直下に.circleciディレクトリを作成し、config.ymlファイルを配置する必要があります。
また、今回は必要なライブラリをインストール済みのカスタムイメージを利用するため、Dockerfileを用意する必要があります。このDockerfileの配置に関しても[公式が配置箇所を指定](https://circleci.com/docs/ja/2.0/custom-images/#dockerfile-%E3%81%AE%E4%BD%9C%E6%88%90)しているのでそれに従います。

```
├── adapter
├── domain
├── usecase
├── hogehoge
└── .circleci/
    ├── config.yml
    └── images
        └── go
            └── Dockerfile
```

## 1. Dockerfile用意

今回はGo言語での開発のため、予めライブラリを```go get```したカスタムイメージを作成して利用することにします。
depも利用しているのでバージョン指定をしてdepのインストールも行います。
CircleCi純正のDockerイメージをベースに、カスタムしました。

```:.circleci/images/go/Dockerfile
FROM circleci/golang:1.11

ENV GOPATH /go
ENV DEP_VERSION 0.5.0

RUN curl -L -s https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -o $GOPATH/bin/dep
RUN chmod +x $GOPATH/bin/dep

RUN go get -u golang.org/x/tools/cmd/goimports
RUN go get -u github.com/kisielk/errcheck
RUN go get -u golang.org/x/lint/golint
RUN go get -u bitbucket.org/liamstask/goose/cmd/goose
RUN go get -u github.com/client9/misspell/cmd/misspell
```

## 2. DockerHubにアップロード

CircleCIからDockerfileを利用する場合、用意したDockerfileをDockerHubにアップロードする必要があります。
方法としては直接アップロードする方式とGitHub等と連携して自動で更新する方法があるようです。今回は直接アップロードを行います。
また、下記操作をする前に予めリポジトリは任意の名前で作成してください。

```
$ cd path-to-project # プロジェクト直下へ
$ docker build --tag=hogehoge/foo:1.0.0 .circleci/images/go # tagは作成した任意のもの&初回は1.0.0が無難
$ docker login
$ docker push hogehoge/foo # tagは作成した任意のもの
```

## 3. CircleCIプロジェクト設定

CircleCIをGitHubに連携する方法に関しては[こちらの記事](https://qiita.com/tatane616/items/8624e61473a9957d9a81)がとても参考になりました。
今回は割愛します。
サンプルのconfig.ymlを作成するところまで進めてください。

## 4. config.yml用意

config.ymlはCircleCIのyml書式に従って記述します。
作成したconfig.ymlはコミットしてmasterブランチまで反映させてください。

```yml:.circleci/config.yml
version: 2

jobs:
  check:
    environment:
      - GOPATH: /go
      - APP_ENV: circleci
    working_directory: /go/src/github.com/path-to-project
    docker:
      - image: hogehoge/foo:1.0.0
        auth:
          username: hogeuser
          password: $DOCKERHUB_PASSWORD
      - image: circleci/mysql:5.7-ram
        environment:
          MYSQL_ROOT_PASSWORD: rootpw
          MYSQL_DATABASE: test
          MYSQL_USER: circleci
          MYSQL_PASSWORD: circleci
      - image: circleci/redis:5.0-alpine

    steps:
      - checkout

      - run: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s
      - run: dockerize -wait tcp://127.0.0.1:6379 -timeout 120s

      - restore_cache:
          keys:
            - v1-vendor-{{ checksum "Gopkg.lock" }}
      - run:
          name: ensure
          command: |
            if [ ! -d vendor ]; then
              dep ensure -vendor-only
            fi
      - save_cache:
          key: v1-vendor-{{ checksum "Gopkg.lock" }}
          paths:
            - vendor

      - run: goose -env circleci up

      - run: go test -v ./...
      - run: find . -type f -name '*.go' -not -path "./vendor/*" -print0 | xargs -0 goimports -l | xargs -r false
      - run: find . -type f -name '*.go' -not -path "./vendor/*" -print0 | xargs -0 misspell -error
      - run: # その他もろもろの確認処理

workflows:
  version: 2
  setup-install-check:
    jobs:
      - check
```

## 5. CircleCIプロジェクト設定の調整

上記の設定のみでは要件が満たせないため、プロジェクトの設定を調整します。

### 実行タイミングの設定

初期設定ではすべてのコミットに対してCircleCIが実行されます。
今回はPull Request時のみにCircleCIを実行したいため、設定を変更します。

CircleCIプロジェクトからSetting > Advanced Settings に進み、「Only build pull requests」をOnに設定。
![スクリーンショット 2019-10-21 18.29.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401312/78892116-5e42-84da-f3cb-cf5a82a03927.png)

これによって、PR時にのみ実行されるようになりますが、defaultブランチに対してのコミットは常に実行される点、注意です。
※defaultブランチはリポジトリごとに設定可能です。GitHubのSettingsから確認をしてみてください。

### Slack通知の設定

CircleCIの実行結果をSlackに通知する設定を入れます。

CircleCIプロジェクトからSetting > Chat Notifications に進み、Slackの「Webhook URL」を設定。
SlackのWebhook URLは画面内のCircleCI Integrationリンクから遷移して取得することが可能です。
![スクリーンショット 2019-10-21 18.30.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401312/61525d90-6712-7d32-b2ba-f5f1fad98178.png)

### 環境変数の設定

DockerHubにカスタムイメージを置く際、セキュリティの関係でプライベートリポジトリを使う必要がある場合があります。カスタムイメージを取得する際に、CircleCIがユーザ情報を保持する必要があるのですが、config.ymlに記載するのは良くないでしょう。
そのため、環境変数にカスタムイメージ取得用アカウントのパスワードを持たせ、config.ymlから呼び出しています。

CircleCIプロジェクトからSetting > Environment Variables に進み、Add Variableをクリック。
![スクリーンショット 2019-10-21 18.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401312/c5dba67f-1162-eef7-6669-bc109e17ddd8.png)

出てきたダイアログに環境変数名とその値を記載してAdd Variableをクリック。
![スクリーンショット 2019-10-21 18.32.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401312/162edbd9-a014-0a43-ad92-2ff2d637563c.png)

# config.yml説明

ポイントだけ少し補足説明します。

## Docker周り

今回はプライベートリポジトリにアップロードしているカスタムイメージを利用するため、最初の```image```(実体はGo用のイメージ)に対して```auth```を設定しています。
```$DOCKERHUB_PASSWORD```は前項で設定した環境変数の値が適応されます。

MySQLイメージのenvironmentに各設定値を入力することで、その設定で起動されます。```MYSQL_DATABASE```を設定することでtestという名前のDBスキーマも作ることができます。

```yml
    docker:
      - image: hogehoge/foo:1.0.0
        auth:
          username: hogeuser
          password: $DOCKERHUB_PASSWORD
      - image: circleci/mysql:5.7-ram
        environment:
          MYSQL_ROOT_PASSWORD: rootpw
          MYSQL_DATABASE: test
          MYSQL_USER: circleci
          MYSQL_PASSWORD: circleci
      - image: circleci/redis:5.0-alpine
```

## DB起動待機

今回はredisとmysqlのDockerイメージを利用するが、それらの起動を待つために下記の処理を行っています。

```yml
      - run: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s
      - run: dockerize -wait tcp://127.0.0.1:6379 -timeout 120s
```

## キャッシュの利用

depを利用して取得したライブラリはvendorに格納されるため、vendorをキャッシュ化しています。
depはライブラリの依存関係がGopkg.lockに反映されるため、Gopkg.lockのチェックサム値をキーとしてキャッシュを保存します。

処理としては下記の流れです。

1. キャッシュが存在したら取得する(restore_cache)
1. キャッシュが存在しなかった（vendorがリストアされなかった）場合、dep ensureを行う。
1. vendorをキャッシュに保存する(save_cache)

なお、dep ensureの際にGopkg.lockが更新され、キャッシュ保存時にキー値が変わってしまう恐れがあるため -vendor-onlyをつけています。

```yml
      - restore_cache:
          keys:
            - v1-vendor-{{ checksum "Gopkg.lock" }}
      - run:
          name: ensure
          command: |
            if [ ! -d vendor ]; then
              dep ensure -vendor-only
            fi
      - save_cache:
          key: v1-vendor-{{ checksum "Gopkg.lock" }}
          paths:
            - vendor
```

※ 保持しているキャッシュを削除したい場合は、v1-vendor-の数値を書き換えるらしいです。

# GitHub側の設定（追記2020/04/14）

上記でCircleCIの設定は完了です。Pull Request生成時にCircleCIは起動します。
しかし、Pull Requestの画面には表示されないため、追加で設定をすると良いです。

GitHubのリポジトリのSettingタブ > Branches > Branch protection rules
でブランチのマージ前等の設定が可能です。
新規にルールを設定する場合は`Add rule`、既存の設定を変更する場合は`Edit`をクリックします。

`Require status checks to pass before merging`にチェックを入れると、直近にGitHubプロジェクトのステータスを更新した要素が表示されるため、自分が作成したものにチェックを入れて保存してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401312/74303bbf-a4d9-364b-4567-439d18667b11.png)

なお、作成したものが出てこない場合、CircleCIのProject Setting内の`GitHub Status Updates`のチェックが外れている可能性があるので、チェックを付け、空コミット等をしてリポジトリ内でCIを起動しましょう。

すると、Pull Requestの画面でCIの結果が見れるようになるはずです。

# おわりに

Goのサンプルが少なめだったので参考になれば嬉しいです。