GitHub + Jenkins で、全てのプルリクエストに対してレビューとテストを必須にする

最近関わった某OSSの真似をして、GitHubプロジェクトでプルリクエスト（PR）のレビューと自動テストを必須にしたいと思い、ドキュメントを適当にググりながら設定してたのですが、書いてる内容が古かったりしてやたらこんがらがり、1日仕事になってしまいました。
ということで、自分はこう設定したというメモを残します。まぁこれもほっとくとすぐ古くなりそうですが。
やりたいことは、以下の通り。

- masterブランチPR発行時、然るべき人にレビューのリクエストが飛ぶようにする
- 同時に、Jenkinsでテストも実行される
- レビューとテスト、両方が通らないとmasterにマージできないようにする

## まずはレビューを必須に

レビューを必須にするのは、GitHubだけで設定可能です。以下説明。
GitHubプロジェクトを作るところは省略します。ここでは、`bonotake/rvtest` なるリポジトリをこさえたとします。

### コードオーナーを設定する
現在のGitHubは、プロジェクト内のコードのそれぞれにオーナーを設定できます。これは、`CODEOWNERS`なるファイルを所定の場所（以下の例では`.github`以下）に置くことで有効になります。
詳しくは[公式ドキュメント（英語）](https://help.github.com/articles/about-codeowners/)を参考にしてもらうとして、ここではこんな感じでファイルを書きます。こう書くと、リポジトリ内の全てのコードのオーナーにGitHubアカウント`bonotake`が設定されます。ここには個人アカウントのほか、Organizationアカウントの場合はその中で作ったチームも指定できます。
![スクリーンショット 2017-11-23 18.27.09.png](https://qiita-image-store.s3.amazonaws.com/0/202213/34307bd5-99db-6331-7775-e07cd0bad001.png)

そして、このファイルを`master`ブランチにコミットしてプッシュ。
その他、一揃い必要な（PRする必要のない）ファイルは一緒に、今のうちにプッシュしときます。
以降の設定すると、`master`への直接プッシュは（設定解除しない限り）基本できなくなるので。

### masterブランチをプロテクトする 
GitHubの、プロジェクトのページに飛んで、"Settings"をクリック。
左側のメニューから”Branches"を選びます。
**Protected Branches**なる欄があるので、そこの”Choose a branch..."で`master`を選択。
"Branch protection for master"なるページに遷移するので、そこで、

1. "Protect this branch" にチェック
2. サブアイテム"Require pull request reviews before merging"にチェック
3. さらにその下の"Require review from Code Owners"にチェック 
4. とりあえず画面下の"Save changes"をクリック

これで、masterブランチに対するPRが発行されると、コードオーナーにレビューがリクエストされて、そのオーナーのうちの誰かがレビュー・承認しない限り、PRをマージできなくなります。

試しにPRしてみると、こんな感じ。
![スクリーンショット 2017-11-23 19.02.19.png](https://qiita-image-store.s3.amazonaws.com/0/202213/b33bea26-db1c-9854-2df0-71e7603d2f54.png)
"Code owner review required"となって、bonotake にレビューがリクエストされます。
ついでにいうと、このとき bonotake にはリクエストメールが飛びます。
でbonotakeがこのPRのページを開くと、右上に"Add your review"のボタンが。
![スクリーンショット 2017-11-23 19.06.30.png](https://qiita-image-store.s3.amazonaws.com/0/202213/19a54513-4696-6317-d7bd-9ec3a4cc8f58.png)

押すと、レビューが入力できるウィンドウが現れます。

![スクリーンショット 2017-11-23 19.08.37.png](https://qiita-image-store.s3.amazonaws.com/0/202213/80ea3e74-948b-0636-de62-e276aa40539a.png)
コメントを入れられるフォームと、その下に3つの選択肢が出ますが、

- **Comment**: とりあえずコメント
- **Approve**: PR承認
- **Request changes**: PR却下

です。
ここのフォームにコメント入力すると全体へのコメントになりますけど、コードの行ごとにコメント入れることもできます。
で、承認すると、こんな感じでマージ可能に。
![スクリーンショット 2017-11-23 19.15.56.png](https://qiita-image-store.s3.amazonaws.com/0/202213/971abd73-edc0-d094-6083-2a2077fa86e5.png)

## 自動テストも必須に
こっからはJenkinsも使います。Jenkinsサーバそのもののセットアップは省略。

### GitHub側：botアカウントの作成
JenkinsがアクセスするためのbotアカウントをGitHubに作成します。
そして、プロジェクトの"Settings"から、"Collaborators"メニューを選択、作ったbotアカウントをcollaboratorに追加します。
botアカウントにinvitationメールが送られるので、そっち側でログインし直してaccept、ログアウトして、元のアカウントでログインし直します。

### GitHub側：Webhookの設定
"Settings"から、"Webhooks"メニューを選択。
"Add webhook"ボタンを押して、遷移してで出てきたフォームに入力していきます。
![スクリーンショット 2017-11-23 23.30.48.png](https://qiita-image-store.s3.amazonaws.com/0/202213/39a00b91-32e1-cc62-bd65-bcd181a78e07.png)

- **Payload URL**: "http://[Jenkinsサーバ]/ghprbhook/"を入力
- **Which event would you like to trigger this webhook?**: "Let me select individual events."を選択

さらに、その下に出てくる項目で、

- "Pull request"にチェック
- "Push"のチェックを外す

とします。
![スクリーンショット 2017-11-23 19.32.21.png](https://qiita-image-store.s3.amazonaws.com/0/202213/2b381564-a92b-c29a-85ca-277a6a3f8591.png)
最後に、”Add webhook"ボタンを押します。

ここで注意なんですが、Jenkinsサーバにアカウントを設定して、ログインしないと各種操作ができない設定にしている場合、上記の設定をしてもダメな場合があります。
そのときは、最初に入れるURLを**"http://[ユーザID]:[パスワード]@[Jenkinsサーバ]/ghprbhook/"**とするとうまくいきました。
セキュリティ的にどうなの、という気がしなくもないですが、~~これ以外のよりセキュアな方法が見つからず……~~ ありました。
上では空欄にしてたんですが、**Secret**の欄に、適当な文字列を入れてください。長めのランダムな文字列が良いと思います。
この文字列を「合言葉」として、Jenkins側にも設定すれば、ユーザIDとパスワードをURLに直書きする必要はないです。
あと、パスワードの代わりにトークンを用いる方法もあるようです。詳しくは[こちらの記事](https://developer.aiming-inc.com/infra/jenkins-github-webhook-collaboration/)を参照。

### Jenkins側：botアカウントの設定
今度はJenkinsの設定。
あらかじめ、Git plugin, GitHub plugin, GitHub Pull Request Builder plugin の3種はインストールしておきます。（「Jenkinsの管理」→「プラグインの管理」からできます。）
そして、さっき作ったbotアカウントをJenkins側に設定します。
「Jenkinsの設定」→「システムの設定」を選択し、遷移した画面の「GitHub Pull Request Builder」欄で

- **GitHub Server API URL**: "https://api.github.com" と入力
- **Credentials**: 
  1. 「追加」ボタン→「Jenkins」をクリック
  1.  現れたダイアログの「ユーザー名」「パスワード」に、さっき作ったbotのアカウント名とパスワードを入力、「追加」ボタンをクリック
  1. プルダウンで、botアカウントが選べるようになるので、選択。
  2. 「Test Credentials...」で、GitHubに接続できるか試せます。ここは省略しますが、一通り試しといたほうがいいでしょう。
- **Description**: なんか区別のつく文字列を。ここでは"bot"とだけ入力。

[追記]さっきGitHubのWebhookで"Secret"に文字列を入れた場合、**Credentials**の欄、"Shared Secret"のところに同じ文字列を入力します。[追記終わり]

以上やって、「保存」ボタンをクリック。

### Jenkins側：ジョブの設定
続いて、ジョブを作ります。とりあえず今回は「フリースタイル・プロジェクトのビルド」でジョブを作成。
ジョブの設定画面で、

- GitHub projectにチェック
  - その下に現れる欄に、GitHubのURL（https://github.com/bonotake/rvtest とか）を入力
- 「ソースコード管理」で"Git"を選択
  - 「リポジトリURL」欄に、リポジトリのURL（https://github.com/bonotake/rvtest.git とか）を入力
  - 「高度な設定…」ボタンを押して、「Refspec」欄に "+refs/pull/\*:refs/remotes/origin/pr/\*" と入力
  - 「ビルドするブランチ」欄を~~空欄に~~ここに`${ghprbSourceBranch}`（PRのソースになってるブランチを指定）と入れます。本来は`${sha1}`（PRを指定）と入れれば良いはずなんですが、僕の環境ではどうしてもうまく動きませんでした。
- 「ビルド・トリガ」で"GitHub Pull Request Builder"にチェック
  - GitHub API Credentials に "https://api.github.com:bot" （コロン以降はさっきの"Description"に入れた文字列）を選択
  - 更に、"Use github hooks for build triggering" にチェック。ここにチェック入れなかった場合は、cronでJenkins側から定期的にポーリングされます。さっきのGitHub側のWebhookが要らなくなる代わり、Jenkins側の負荷が多少増えるし、PR発行とJenkins起動にタイムラグが発生する場合があるので、ここではチェックを入れます。
  - 「高度な設定」を開き、"White list" にPRしそうなGitHubユーザのアカウント名をあらかじめ入力。ここで指定した人からのPRでのみJenkinsが走ります。
    - → あるいは、"List of organizations. Their members will be whitelisted"にOrganizationアカウントを指定すれば、そのOrganisationに所属する任意のユーザのPRからJenkinsが起動します。
    - → あるいは、"Build every pull request automatically without asking (Dangerous!)."にチェックを入れれば、どんな人のPRからでもJenkinsが走るように。ただし、悪意あるユーザが変なPRをすれば、そのコードでJenkinsサーバを自由に動かせてしまうので、要注意。
    - 同じく「高度な設定」、"Whitelist Target Branches:"に master と入力。これで、masterブランチに対するPRだけがJenkinsビルドの対象になります。
- 「ビルド」で「ビルド手順の追加」をクリック、「シェルの実行」を選択
  - ここに、具体的に実行するテストスクリプトを入力します。詳細は割愛。各人ご自由に。

以上終わったら、「保存」をクリック。

### GitHub側：実際にPRをしてみる
実はまだ設定終わってないんですけど、いっぺんPRをやってみないと先に進めないという、若干**バッドノウハウ**感の漂う手順が存在するので、とりあえずPRします。内容はとりあえずなんでもいいです。
うまくいくと、このPRでJenkinsが起動して、ステータスがPRの画面に表示されます。
![スクリーンショット 2017-11-23 22.35.09.png](https://qiita-image-store.s3.amazonaws.com/0/202213/a14c6556-ddb5-9e68-e6e6-e077f8253da4.png)
こんな感じで、×がついてる（テスト失敗）んですが、その結果は関係なく、レビューが通っていればマージできる状態。
これを、Jenkinsのビルドが失敗すればマージできない状態にします。

### GitHub側：テストを必須に
"Settings"から"Branches"メニューを開き、"Protected branches"のところ、先に`master`が選択されてますが、その右横のEditボタンを押します。
ここで、さっきチェックしなかった**Require status checks to pass before merging**にチェック。
"Status checks found in the last week for this repository"のところ、"default"という選択肢が出てますが、これがJenkinsのビルドによるチェックです。この欄が、一回Jenkinsを起動させないと現れないので、まず先にPRをしてみる必要があるのでした。
ということで、この"default"にチェック。右横に"Required"の文字が現れます。
![スクリーンショット 2017-11-23 23.11.13.png](https://qiita-image-store.s3.amazonaws.com/0/202213/feea8310-2631-d808-df35-bfbd3eca40ec.png)
画面下の"Save changes"ボタンを押し、先程のPRに戻ってみると、表示がちょっと変わっています。
![スクリーンショット 2017-11-23 23.16.49.png](https://qiita-image-store.s3.amazonaws.com/0/202213/254372df-f5f6-bf62-fcb5-319731ba5e71.png)

Administratorならマージできるんですが、基本はダメよ状態に。
ここで、ちゃんとテストの通るコミットをしなおしてやると、

![スクリーンショット 2017-11-23 23.24.01.png](https://qiita-image-store.s3.amazonaws.com/0/202213/8dd99231-46bb-847d-950e-236dfbdb4d74.png)

無事マージできるようになりましたとさ。

[追記]
でも、テストやってるのに表示が"default"って何だかわからないし、テスト失敗してるのに"Finished."って出るのも何だか変だし……と思われるかもしれません。
その場合、ジョブの設定で「ビルド・トリガ」欄の"GitHub Pull Request Builder"以下にある"Trigger Setup..."ボタンを押して、出て来る入力フォームに以下の要領で入力してください。

- **Commit Status Context**: "default"の代わりに表示するジョブの名称
- **Commit Status Build Result**: ジョブの実行結果（SUCCESS/ERROR/FAILURE）のそれぞれに応じて表示するメッセージ（「追加」ボタンで各メッセージを追加できます）

例えばこんな感じ。

![スクリーンショット 2017-11-24 11.04.59.png](https://qiita-image-store.s3.amazonaws.com/0/202213/bb6b2919-aa43-1cb4-503e-fb8d4705887e.png)

ジョブの名称を"test"にして、あと、SUCCESS/ERROR/FAILUREそれぞれにメッセージを変えて入れてます。
で、これを設定後、改めて適当にPRをし（汗）、Require status checks... のところの設定をやり直してください（"default"のチェックを外し、"test"にチェックを入れる）。
ジョブごとに名称やメッセージを変えられるので、複数のジョブを定義して、複数のテストを並列実行させ、それぞれの成功を必須にする、なんてこともできます。

注意してほしいのですが、この設定はジョブ単位でやってください。**システム単位でもできますが（「Jenkinsの管理」→「システムの設定」）、まともに動作しないので触らない方が無難です。**


## 参考記事とか
- [Pull Request Builder PluginをJenkinsに導入する](https://qiita.com/kompiro/items/a097b3b36aa30bc751c6)
    - ~~ここに書かれてる「Refspec」の欄の設定が、恐らくMarkdownのエスケープミスでおかしくなってて、このままだとJenkinsがエラーを吐きます。正しくは上記。~~修正されました。
- [GitHub Pull Request Builder Plugin の多すぎる設定項目を補足してみる](https://qiita.com/naotawool/items/e7fef0b2de7f070d891d)
- [Jenkinsでプルリクエストをビルドする](https://qiita.com/quattro_4/items/6b1962909ce868f12e5a)
- [gitbucketとjenkinsをGitHubPullRequestBuilderを使って連携する](https://qiita.com/hikaruworld@github/items/ae2cdc1904d7d35ef0b8)
- [hudson - Jenkins and GitHub webhook: HTTP 403 - Stack Overflow](https://stackoverflow.com/questions/7427557/jenkins-and-github-webhook-http-403)
- [About pull request reviews - GitHub User Documentation](https://help.github.com/articles/about-pull-request-reviews/)