Edited at

CircleCIを使ってbundle updateを定期実行する

More than 3 years have passed since last update.


自動実行の流れ

a0c0b578-7cf8-2a98-0bf5-ccd21005aa8e.png


1. CircleCIを起動する

Nightly builds というAPIを使います。本来は重たいテストを夜中に1回だけ実行する、みたいな用途で使うことを意図しているのでNightly buildsという名前になっていますが、簡単な話が「外から環境変数を追加した上で、特定のブランチに対するテストを実行させる」機能です。

BUNDLE_UPDATE という環境変数を設定した上でmasterブランチをビルドするとbundle updateが実行されるようにすることにしました。以下のようなスクリプトを用意し、cronで平日の朝に自動実行するようにしています。

_project=user-name/repo-name  # 適当に変える

_branch=master
_circle_token=$1

trigger_build_url=https://circleci.com/api/v1/project/${_project}/tree/${_branch}?circle-token=${_circle_token}

# BUNDLE_UPDATE環境変数をつけてCIを走らせる
post_data='{ "build_parameters": { "BUNDLE_UPDATE": "true" } }'

# Nightly buildsを実行
curl \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "${post_data}" \
--request POST ${trigger_build_url}


2. CIサーバ上で bundle update を実行する

BUNDLE_UPDATE かつmasterブランチの場合は bundle update だけを実行するようにします。1


circle.yml

test:

post:
- >
if [ -n "${BUNDLE_UPDATE}" -a "${CIRCLE_BRANCH}" = 'master' ] ; then
bundle update
fi


3. Gemfile.lockが変更されている場合はプルリクエストを作る


circle.yml

deployment:

auto-bundle-update:
branch: master
commands:
- >
if [ -n "${BUNDLE_UPDATE}" ] ; then
bash script/circleci/create_pull_request_if_needed.sh
fi


script/circleci/create_pull_request_if_needed.sh

export BRANCH=bundle-update-`date -u "+%Y%m%d"`

if [ -n `git status -sb 2> /dev/null | grep Gemfile.lock` ] ; then
git config --global user.email user@eample.com
git config --global user.name 'your name'
git add Gemfile.lock
git commit -m 'Bundle update'
git branch -M $BRANCH
git push origin $BRANCH
bundle exec ruby script/circleci/create_pull_request.rb
fi

プルリクエストを作る部分はrubyで作りました。


script/circleci/create_pull_request.rb

require 'octokit'

client = Octokit::Client.new(access_token: ENV['GITHUB_ACCESS_TOKEN'])
client.create_pull_request(
'user-name/repo-name', # 適当に変える
'master',
ENV['BRANCH'],
'Bundle update', # Title
'' # Body
)



4. githubのhookでCircleCIが起動され新しく作られたブランチのテストが走る

ここからは通常のCIと同じです。テスト結果に応じて3で作られたプルリクエストを煮るなり焼くなりします。


その他の話題


背景


bundle updateとは

bundle update は依存しているgemに新しいバージョンが公開されている場合、その新しいものを使うように更新する作業のこと。bundlerは明示的に bundle update を実行しなければ、同じgemの同じバージョンをずっと使い続ける。


なぜupdateする必要があるのか

bundle update せずに放置すると、古いバージョンに含まれるバグ :bug: の修正や、パフォーマンスの改善、機能追加などの恩恵を享受することができない。逆に言えば、その果実を回収するために bundle update を実行する。


なぜ定期実行する必要があるのか

bundle update を実行すると、場合によってはアプリが意図した通りに動かなくなることがある。これは bundle update によって変更されるバージョンの差分が大きければ大きいほど発生しやすくなり、また確認すべきドキュメント、コミットの数が増えるため対応するコストが大きくなる。大量のgemが一度にアップデートされると、仮に問題が発生した時に、それがどのgemを更新したことによって発生するようになったのかを切り分けることが難しくなるという問題も発生する。

定期実行すれば、大きなジャンプを経験する可能性が減るため、仮に問題が起きても対応するのが容易になる。


なぜ自動化する必要があるのか

理由は単純に「忘れてしまう」ため。忘れてしまうと大きなバージョンチェンジにいっきに対応する必要が発生し、前述の通り追従にかかるコストが増加するのでますます bundle update する気がなくなってしまう悪循環が発生する。


関連実装

bundlerに限らず色々な言語のライブラリ管理ツールをアップデートするためのgem。抽象化しなくても十分に理解できるレベルだと思ったので使わなかった。

上のgemをSaaSとして提供しているもの。リポジトリへのアクセス権限を渡す必要があるので :no_good: publicリポジトリの自動更新をしたい場合は、これを使った方がいいと思う。





  1. テストの実行もスキップするようにしています。