logo

こんにちは、kyoです。
今回はPull Requestの開発・レビューを効率化するツールDangerを紹介します。
紹介とは言え、具体的なインストール方法や設定などはドキュメントや関連記事を参照すれば大丈夫だと思いますので、この記事は主にDangerは何を、どうやって開発・レビューの効率を上げることに集中します。

Dangerとは?

DangerはCIサービス上で動作し、Pull Requestに対して自動チェック(だけじゃない)を行うossです。
現在コミュニティではメインのrubyプロジェクトdanger/dangerとnodejsで書き換えるdanger/danger-jsの2つが主に開発を進めています。
私(leonhartX)も微力ながらDangerの開発者の一人で、dangerのrubyプロジェクトやdanger-swiftlint, danger-android_lint の開発に関わっていて、自分自身もdanger-lgtmdanger-eslintなどのdangerプラグインを開発しています。
この記事は現在のメインプロダクトであるrubyの方を説明します。

Dangerは何ができる?

Githubのdescriptionでは

Stop saying "You forgot to..."

と書いており、基本的にはレビューの自動チェックですが、実に言うとCIのプロセス中に、Pull Requestの情報を参照できる状態で(rubyを使って)やりたいことをし、(必要な時に)その結果をPRにコメントという形でoutputするものです。

Dangerを動かすには、レポジトリにDangerfileというrubyのファイルを用意する必要があります。
Dangerfileが実行される時、いくつかの便利module/objectが利用できます、例えば:

  • PRのgit情報(変更したファイル, diffなど)にアクセスできるgit
  • ソースコードのhostingサービスとやりとりをするgithub,gitlab,bitbucket cloudなど
  • PRにコメントを追加するためのメソッドfailwarnが入ってるmessaging

また、CIプロセス中のため、ソースコードもcheckoutされているのですべてのソースファイルもアクセスできます。

これだけの情報を持っていれば、明文化できるルールやチームのお作法的なものは簡単に自動チェックできると思います。
ここで公式ページで紹介されている導入例の使い方をいくつかリストアップします(Dangerのコメントイメージです):

  • "WIPが付いてますよ"
  • "ラベル付けてください"
  • "このPRはソースを変更していないよ"
  • "PRの変更内容が多すぎます"
  • "ソースコード追加したのにテストコードが追加されてない!"
  • "CHANGLOG書いてください"
  • "merge commitがあるのでrebaseしてください"
  • "TODOが残ってますよ"
  • などなど

要は人で無くてもチェックできるものをDangerにまかせて、自動的にチェックの結果をコメントと言う形で可視化します。
こういったチェック機能自体は頑張ればCIサービス内でもできなくはないです、例えばJenkinsのジョブ設定やcircle.yml/travis.ymlの記述の中でshellコマンド叩きまくりとか、しかしDangerはそれを簡単に記述でき、さらにフィードバックもCIの結果ではなく直接PR上で反映できるところのポイントが高いと思います。

Danger Plugin

Danger自身の機能だけでもかなりありがたいですが、Pluginを使えばさらに強力になります。
Pluginは共通の機能をモジュール化したもので、複数プロジェクト間の機能の共有とDangerfileの軽量化に役に立ちます。
公式ページ上でもたくさんのPluginを紹介してます、例えばlint系のもの(swiftlint, android_lint, eslint)はソースに対してlintを実行し、指摘の結果をPR上のコメント、またはPRの変更ソース上に直接インラインコメントをします。

pic1

Pluginを主に他のライブラリやツールとの連携が多いです、特に静的検査系のツールとはかなり相性がいいと思います。
他にもテストのカバレッジを表示したり、ランダムでレビューアーを選定してメンションしたり、指摘が一個もなければlgtm.inからランダムの画像を貼ったりという面白いPluginもあります(さり気なく自己宣伝)。

Danger活用

既存のPluginを導入するだけでDangerの活躍できる分野が一気に広がりますが、ここまで読んだ皆さんはもうお気づきでしょう、Dangerの本質はPRとCIの間にプログラミング可能な環境を用意しているということです。
プログラミング可能ということは、単純なチェック自動化の枠を超えていろんな面でプロセスを改善できる可能性があると思います。

ここでリクルートジョブズのはたらいくiOSアプリで使っているDangerfileはをサンプルとして解説します:
Dangerfileの全体は下記の通りです:

# create a .ci_skip file to tell jenkins skip the test stage
if git.commits.first.message =~ /\[ci skip\]/ || github.pr_body =~ /\[ci skip\]/
  warn "Test stage is skipped"
  File.open(".ci_skip", "w")
end

# add JIRA's ticket url when tilte contains ticket number
match = github.pr_title.match /\[(HATAPP-\d+)\]/
message "JIRA Ticket: <a href='http://jira.example.com/jira/browse/#{match[1]}'>#{match[1]}</a>" if match

# a PR without any changes is invalid
fail "This PR has no changes at all" if git.modified_files.empty? &amp;&amp; git.added_files.empty? &amp;&amp; git.deleted_files.empty?

# check release note has changed when a release/* branch is created
warn "Please change release note in <a href='https://ghe.example.com/HAT/HatalikeSwift/blob/#{github.branch_for_head}/fastlane/Deliverfile'>Deliverfile</a>" if github.branch_for_head.start_with?("release") &amp;&amp; !git.modified_files.include?("fastlane/Deliverfile")

# run swiftlint
github.dismiss_out_of_range_messages
swiftlint.config_file = '.swiftlint.yml'
swiftlint.lint_files inline_mode: true

# add lgtm pic
lgtm.check_lgtm

まずは一番先頭のところ:

# create a .ci_skip file to tell jenkins skip the test stage
if git.commits.first.message =~ /\[ci skip\]/ || github.pr_body =~ /\[ci skip\]/
  warn "Test stage is skipped"
  File.open(".ci_skip", "w")
end

ここの部分はDangerとJenkinsを連携させ、JenkinsにCircleCIやTravisCIと同じci skipの機能を持たせるようにします。
直近のコミットログ、またはPRの説明文に"[ci skip]"という文字列があれば.ci_skipのファイルを作成し、後続のjenkins処理ではこのファイルを見つかるとテストstageを飛ばすようにしています。またPR上でもテストがスキップされているという警告を表示します。

Jenkinsにはci skipのpluginはありますが、はたらいくではJenkins2.0のGithub Organization機能を利用しているためそのpluginは利用できません

これはPRのチェックとは関係なく、CIの機能補強ということになります。
もちろん文字列のチェックはJenkinsfile中でも実装可能ですが、PR上で可視化するとなるとDangerfileのように簡単にはいけないと思います。

次にこちら:

# add JIRA's ticket url when tilte contains ticket number
match = github.pr_title.match /\[(HATAPP-\d+)\]/
message "JIRA Ticket: <a href='http://jira.example.com/jira/browse/#{match[1]}'>#{match[1]}</a>" if match

はたらいくチームではJIRAでタスク管理をしている、各タスクに対してPRを切って開発を進めますが、そのPRのタイトルにJIRAチケット番号を記入するというチーム内のルールがあります。
DangerではそのPRのタイトルにチケット番号を検出したら自動的にチケットへのリンクを貼り、レビュー時のタスク内容確認がし易いようにしています。

別の話になりますが、はたらいくチームが運用しているchatbotでは、Githubからのwebhookを受信するように設定していて、タイトルにチケット番号が含まれているPRのcloseイベントが受信されたらbotの方で自動的にJIRAタスクをdoneに変更しています。

こちらは一般的な使い方だと思います:

# check release note has changed when a release/* branch is created
warn "Please change release note in <a href='https://ghe.example.com/HAT/HatalikeSwift/blob/#{github.branch_for_head}/fastlane/Deliverfile'>Deliverfile</a>" if github.branch_for_head.start_with?("release") &amp;&amp; !git.modified_files.include?("fastlane/Deliverfile")

はたらいくのiOSアプリはfastlaneのDeliver機能でアプリデプロイしています、リリースノートを含むアプリのメタデータは全部Deliverfileに書いてソース管理しています。
PRのマージ先がreleaseブランチの場合(リリースを準備しているということ)、Deliverfileファイルが変更されていないと"リリースノートを追加してください"の警告を表示します。

ここはPluginの機能を利用してます:

# run swiftlint
github.dismiss_out_of_range_messages
swiftlint.config_file = '.swiftlint.yml'
swiftlint.lint_files inline_mode: true

# add lgtm pic
lgtm.check_lgtm

swiftlintを走らせてPRの修正内容をチェックします。
swiftlint.lint_files inline_mode: trueの設定はlint結果をPRコメントではなくソース上のインラインコメントとして表示するという設定です。
しかしswiftlintはファイル単位で実行するものであり、指摘がPRの修正範囲外の場合、インラインコメントでは表示できず、普通のPRコメントにfallbackになります。
そのような指摘を表示したくない場合(要はこのPRの差分のみ指摘したい)、github.dismiss_out_of_range_messagesを設定すればRPの差分範囲外に対する指摘はすべて無視します。

ちなみにswiftlintのinline_modeとDangerのgithub.dismiss_out_of_range_messages機能は共に筆者が書いたものであり、はたらいくアプリはおそらく初めてこれらを利用したプロダクトでしょう。

最後にlgtmのチェックを実施、上の各ルールやswiftlintの結果は全部問題なければlgtmの画像をランダムではります。
一見ちょっとくだらないですがヒューマンレビューが入る前にbotからlgtmをもらうのは実は結構嬉しいことで、開発も捗るではないかと個人的に思ってます。

pic3

終わりに

今回はDangerの使い方を簡単に紹介しました。
ossや社内のプロダクト開発も、Pull Requestが起点とするdevopsフローが結構一般的になっていると思います、そういう"PR駆動開発"の中が、Dangerをうまく利用すればかなりの効率向上を実現できるではないかと思いますので、皆さんもぜひDangerを試してみてください。
またDangerの開発に興味ある方もPlugin作りやコミュニティーに参加してプロダクトを改善していきましょう、目指せDanger野郎!