18
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CircleCIAdvent Calendar 2018

Day 9

CircleCIとdangerで、どんなプロジェクトでも簡単に「Stop saying "you forgot to …"」

Last updated at Posted at 2018-12-09

CircleCI Advent Calendar 2018の9日目です🎄

8日目の記事は @f-naoto832 さんのCircleCIで試行錯誤した話でした。

はじめに

この記事は
CircleCI2.0danger(ruby)専用のdocker imageを使うことで
どんなプロジェクトでもdangerを利用できるようにした例を紹介します。

主な対象読者

  1. 最終的にPRで、approveする人(Techリードとか?)
  2. ruby, js, swift以外のどんなプロジェクトでもdangerを実行したい人
  3. CIの整備をしている人

きっかけ

  1. ソースコードレビュー時、「〇〇、まだ終わってないんだけど」って毎回指摘するのがめんどくさい。そして、される側も嫌だろう。 :sob:
  2. ruby on railsのプロジェクトにはdangerをGemfileに追加しているが、Scala / Java / Golangとかのプロジェクトでもやりたい。 :sob:
  3. モノリシックだったが、マイクロサービスにして、リポジトリが増えて、メンテしなければならないDangerfileが増えた。:sob:
  4. dangerのバージョンを変更する時や、danger pluginを追加する時に、毎回それぞれのリポジトリのGemfileを変更しなければならない。 :sob:
  5. そもそもサービスに関係ないdangerのgemをGemfileに追加するのはいかがなものだろうか? :thinking:

前提

  1. danger自体の説明は他にもわかりやすい資料があるので、そちらを参考。
    ex. Dangerでpull requestレビューの指摘事項を減らす
    https://www.slideshare.net/ShunsukeMaeda/dangerpull-request
  2. Dangerfileの詳細な書き方に関しては、触れません。
  3. 「Only Build pull requests」関連の話は、別の記事を参考。
    ex. CircleCIでDangerを動かすためにOnly Build Pull Requestsの設定を調べてみた
    https://blog.sshn.me/posts/circleci-only-build-pull-requests/
  4. docker image作成時のcircleciの設定に関しても触れません。

作ったもの

対象 URL
circleci-danger https://github.com/jkkitakita/circleci-danger
dangerfile https://github.com/jkkitakita/dangerfile
dockerhub https://hub.docker.com/r/jkkitakita/circleci-danger/
circleci-danger-sample https://github.com/jkkitakita/circleci-danger-sample

各種設定

Dockerfile

まずは、danger専用のdocker imageのDockerfile.
https://github.com/jkkitakita/circleci-danger/blob/master/Dockerfile

FROM ruby:2.3.1-alpine

ARG VCS_REF
ARG BUILD_DATE
LABEL maintainer="jkkitakita (Jun Kitamura)" \
      org.label-schema.url="http://jkkitakita.com" \
      org.label-schema.build-date=$BUILD_DATE \
      org.label-schema.vcs-url="https://github.com/jkkitakita/circleci-danger"\
      org.label-schema.vcs-ref=$VCS_REF

ENV HOME=/home/circleci

# CircleCIのuserを作成
RUN apk update && apk upgrade && \
  apk add --no-cache bash git openssh && \
  addgroup -g 3434 circleci && \
  adduser -D -u 3434 -G circleci -s /bin/bash circleci

# CircleCIのuserへ切り替え
USER circleci

# Gemfile, Gemfile.lock, Dangerfileをimage内にcopy
COPY --chown=circleci:circleci Gemfile* $HOME/danger/
COPY --chown=circleci:circleci Dangerfile $HOME/danger/

# $HOME/dangerへ移動
WORKDIR $HOME/danger

# bundlerをinstallして、globalにdangerをinstall
RUN gem install bundler --no-document && \
    bundle install

CMD ["/bin/sh"]

ポイント

  1. circleci userを作成しました。(rootじゃなくても、dangerだけ実行できればいいので。)
  2. ciでの実行頻度が高い可能性があるので、なるべく軽くするためにalpineを利用した。(2.4/2.5も試したのだが、うまくいかなかった。。なぜ。。)
  3. Gemfile, Gemfile.lock, Dangerfileにcircleci user権限を付与しながらcopyしないと、権限がない。
  4. 事前にしておけば、あとでbundle installする必要ない。

Gemfile

bundle installする時のGemfile.
https://github.com/jkkitakita/circleci-danger/blob/master/Gemfile

source 'https://rubygems.org'

gem 'danger'

# Add a danger plugin if necessary.
## gem 'danger-plugin_name'

ポイント

  1. gem 'danger'を追加
  2. 必要に応じて、gem 'danger-plugin_name'を追加 (個人的には、LGTMのplugin好きです。笑)

gem 'danger-todoist'
gem 'danger-lgtm'

https://github.com/hanneskaeufler/danger-todoist
https://github.com/leonhartX/danger-lgtm

Dangerfile(共通)

docker imageに含める共通Dangerfile.
https://github.com/jkkitakita/circleci-danger/blob/master/Dangerfile

# Change to the path of the dangerfile you want to import.
danger.import_dangerfile(github: "jkkitakita/dangerfile", branch: "master")

ポイント

  1. danger.import_dangerfileを利用する
    https://danger.systems/reference.html
  2. Dangerfile自体を、別リポジトリで管理するようにしておけば、毎回docker imageを作り直す必要がない
  3. また、CircleCI実行時、このDangerfileを修正してから、dangerを実行するようにすれば、プロジェクト毎に違うルールで、CIを実行できる。

dangerfile/Dangerfile(ex. ruby on rails)

danger.import_dangerfileするDangerfileの例。
https://github.com/jkkitakita/dangerfile

# Alias
BIG_PULL_REQUEST_LINES = 1000
APP_FILES = /^(app|lib)/
TEST_FILES = /^(features|spec|test)/
has_app_changes = !git.modified_files.grep(APP_FILES).empty?
has_test_changes = !git.modified_files.grep(TEST_FILES).empty?

## Check label and body of PR.
has_label_wip = github.pr_title.match(/wip/i) || github.pr_labels.include?('wip') || github.pr_labels.include?('Wip') || github.pr_labels.include?('WIP')
has_pr_body_no_check = github.pr_body.match(/- \[ \]/)

# Request changes and Attention
## TODO in code.
## ref. https://github.com/hanneskaeufler/danger-todoist
todoist.message = "TODO対応してくれないの?:cry:"
todoist.warn_for_todos
todoist.print_todos_table

## WIP
fail('まだWIPだからmergeできないの!') if has_label_wip

## TODO in PR body.
fail('「[ ] チェックリスト」全部終わらせるなの!') if has_pr_body_no_check

## Attention to code
warn('1000行以上の修正は、危ないかもだけどー?') if git.lines_of_code > BIG_PULL_REQUEST_LINES
warn('appのコードを修正してるけど、testは書かないのー?') if has_app_changes && !has_test_changes

## Attention to a PR without any changes
if git.modified_files.empty? && git.added_files.empty? && git.deleted_files.empty?
  message('何も修正してないかもだけどー?')
end

# Procedure for re-executing CircleCI
message(
  "@#{github.pr_author}\n\n"\
  "失敗した時は、指摘内容を修正して\n"\
  "<a href='https://circleci.com/workflow-run/" + ENV['CIRCLE_WORKFLOW_ID'] + "'>https://circleci.com/workflow-run/" + ENV['CIRCLE_WORKFLOW_ID'] + "</a>\n"\
  "から「Rerun from failed」を実行してなの!"
) if !violation_report[:errors].empty?

# LGTM pic
# ref. https://github.com/leonhartX/danger-lgtm
lgtm.check_lgtm

ポイント

  1. APP_FILESTEST_FILESあたりはプロジェクト毎に追加・修正
  2. GitHubのPRの画面上に、CircleCIの「Trigger Jobs」を叩く「ボタン」をmessageに表示させたかったが、できなかったので、workflowのリンクを表示させた。

.circle/config.yml(各リポジトリ側)

version: 2

jobs:
  danger:
    docker:
      - image: jkkitakita/circleci-danger
    steps:
      # ~/projects(defaults)へ、対象リポジトリのsourceをcloseする
      - checkout

      # 共通Dangerfileをコピー
      # 対象リポジトリにすでにあるDangerfileを利用する場合、実行不要。
      - run: cp -f ~/danger/Dangerfile .

      # danger実行
      # --fail-on-errors=trueをつけると、PRでfailになります。
      - run: bundle exec danger --fail-on-errors=true

workflows:
  version: 2
  danger:
    jobs:
      - danger
      # contextを使う場合
      # - danger:
      #     context: danger

ポイント

  1. 共通Dangerfileを利用する場合、~/danger/Dangerfileをカレントへコピーすればok。
  2. --fail-on-errors=trueをつけて実行しないと、jobは成功しているけど、dangerだけ失敗してしまう(rerunできない)
  3. 複数プロジェクトで利用する場合は、contextにした方が、プロジェクト毎にEnvitonmentsを毎回設定しなくて良い。

実際にやってみると...

ScreenShot 2018-12-09 16.45.14.png

:tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada:

  1. GitHub - PR
    https://github.com/jkkitakita/circleci-danger-sample/pull/1
  2. CircleCI - Job
    https://circleci.com/gh/jkkitakita/circleci-danger-sample/8

結論

  1. 業務では、Scala / Java / Golang / js etc...なプロジェクトにこのdanger jobを導入していますが、今の所、問題なく動作しています。
  2. 「ci通したら、レビュー依頼出してもらえますか?」って言うコミュニケーションをしておくだけで、スムーズになった。
  3. Dangerfileの管理が格段に楽になった!!

今後の課題

  1. 業務では、mergeしたら、buildを実行する運用をしているので、「Only Build pull requests」はoffにしているので、Pull Requestをcreateしたら、buildを実行されるようにしたい。今まで↓みたいに、1日1回深夜に実行したりしていたのですが、無駄にbuildが実行されてしまうので、どうにかしたい。
scheduled-workflow:
    triggers:
      - schedule:
          cron: "0 18 * * *"
          filters:
            branches:
              ignore:
                - master
    jobs:
      - danger:
          context: danger

まさに、2日目の、@yasuhiroki さんの
CircleCIのOnly build pull requestsをoffにしてもプルリクエストを作ったらJobを実行したい
が参考にやってみようかなと思う!
2. dangerのdocker imagesのrubyのバージョンをあげたい。(ruby2.3系は2018年12月にサポートが切れるので。。)
3. CircleCIのAdventcalendarで言う事かわかりませんが(笑)、TravisCIでも実行できるようにしたい。

さいごに

「ここは違うだろ!」「ここはこうした方がいいよ!」
とかあれば、いつでも、コメントください。非常に喜びます。

18
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?