前回の記事で、TravisCIからCircleCIに移行したことを書いた。
今回は、テストが通ったら自動的にrake release
する仕組みを作ったので、それについて書く。
ただし、今回のやつは結構面倒であった。
gemをリリースするorbsは既にある
実はもう既にある模様だが、なんとなく実装が好きになれなかった。
https://circleci.com/orbs/registry/orb/doximity/gem-publisher
- gem/nameとgem/versionというファイルにgemの名前とバージョン情報を書かなければならないこと
- rake releaseを使わずにgemをrubygemsにpushしていること
- たぶん、gemの名前が部分一致してしまうものだと機能しない
そこで、とりあえず自分でやってみることにした。が、実装は参考にさせてもらった。
実装
先に実装を載せておく。結構面倒です。何をやっているのかを書いていく。
- チェックアウト
- GitHubにアクセスするためのSSH鍵の登録
- bundle install
- RubygemsにgemをリリースするためのAPI KEYを設定
- gitのセットアップ(githubにタグを打つために必要)
- 同バージョンがまだリリースされていなければ、rake releaseする
- RubygemsにリリースするためのAPI KEY情報を消す
このうち、4,6,7は先ほどのorbsをかなり参考にさせてもらった。
jobs: release: executor: name: ruby parallelism: 1 steps: - checkout - add_ssh_keys: fingerprints: - "e7:e8:17:c5:03:05:fd:0c:0e:9b:3b:d3:90:53:c6:5c" - run: name: bundle install command: bundle check || bundle install --jobs=4 --retry=3 - run: name: RubyGems.org | Set credential command: | mkdir -p ~/.gem echo ":rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials chmod 0600 ~/.gem/credentials - run: name: Setup git command: | git config push.default current git config user.email "chariderpato@gmail.com" git config user.name "patorash" - run: name: rake release command: | gem list --prerelease --all --remote imyou \ | grep -E "^imyou" \ | sed -e "s/^.*(\(.*\)).*\$/\1/" \ | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`" result=$? if [ $result = 0 ]; then bundle exec rake build bundle exec rake release fi - run: name: Delete credentials command: | shred -u ~/.gem/credentials
GitHubにアクセスするためのSSH鍵の登録
rale releaseでタグを打つには、読み書き可能なSSH鍵が必要なため、以下のサイトを参考にして作業した。
SSHの秘密鍵、公開鍵の作成
まず、秘密鍵と公開鍵を作る。
$ cd ~/.ssh $ ssh-keygen -t rsa -b 4096 -m pem -C "CircleCI" -f id_rsa_circleci -N ""
GitHubに公開鍵を登録
次に、https://github.com/[username]/[reponame]/settings/keys にアクセスして、公開鍵を登録する。
id_rsa_circleci.pubの内容を貼り付けて保存する。このとき、Allow write accessにチェックを入れるのを忘れないこと。 保存できたら、もともと登録されていたであろうread onlyの鍵を削除しておく。
CircleCIに秘密鍵を登録
次に、https://circleci.com/gh/[username]/[reponame]/edit#ssh にアクセスして、秘密鍵を登録する。
id_rsa_circleciの内容を貼り付けて保存する。
その後、https://circleci.com/gh/[username]/[reponame]/edit#checkout にアクセスして、デプロイキーを削除する(これがread onlyのやつの秘密鍵)。
RubygemsのAPI KEYを環境変数に保存
RubygemsのAPI KEYはRubygemsにログインしてプロフィールの編集画面にいったら書いてある。
この値を、CircleCIの環境変数に登録する。変数名は、RUBYGEMS_API_KEY
にする。
gitの設定
タグを打つ時にgitの設定が必要なので行なっておく。
rake releaseする
テストが通ったらrake releaseしたいが、もしgemのバージョンアップを含まない変更だった場合、実行されてしまうと既にリリース済みというメッセージと共にCIが落ちるので、バージョンアップしていない場合は無視したい。
先のorbsの実装のコマンドを実行したところ、gem list 〜
の結果に自分のgemとは異なるgemまで出てきた。
$ gem list --prerelease --all --remote imyou 16:12:18 *** REMOTE GEMS *** bimyou_segmenter (1.2.0, 1.1.1, 1.0.1) imyou (1.3.1, 1.3.0, 1.2.0, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.0)
imyouというgem名なのだが、bimyou_segmenterまで引っかかった。bimyou_segmenterという名前なので仕方がない…。なので、imyouで先頭一致する行を抽出した。
$ gem list --prerelease --all --remote imyou | grep -E "^imyou" imyou (1.3.1, 1.3.0, 1.2.0, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.0)
あとは、バージョン情報だけ抽出し(括弧の中のみ)、現在のgemのバージョンの文字列があるかどうかチェックした。
gem list --prerelease --all --remote imyou \ | grep -E "^imyou" \ | sed -e "s/^.*(\(.*\)).*\$/\1/" \ | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`" result=$? if [ $result = 0 ]; then bundle exec rake build bundle exec rake release fi
細かいことを言うと、1.3.1をリリースする前に1.3.1.preとかがあると、ヒットしてしまって正式リリースがこのワークフローでは行えない。しかし、文字列を分割して配列に入れて完全一致するバージョンの有無を調べるのがbashだと面倒そうだったので、こうしている。簡単な方法があれば採用したい…。
あと、gem名とバージョン情報がハードコーディングになっている。多分、これがうまく取れないからorbsの作者も、gem/nameとgem/versionというファイルを保存するという苦肉の策をしているのだろう…。ここをエレガントに取得する方法があったら知りたい。
ワークフローに組み込む
テストが通ったらrake releaseする(ただしmasterブランチのみ)とした。
workflows: build: jobs: - test: name: 'Ruby 2.4.5-Rails 4.2' ruby_version: '2.4.5' rails_version: '4.2' - test: name: 'Ruby 2.5.5-Rails 4.2' ruby_version: '2.5.5' rails_version: '4.2' - test: name: 'Ruby 2.6.2-Rails 4.2' ruby_version: '2.6.2' rails_version: '4.2' # 略 - release: requires: - 'Ruby 2.4.5-Rails 4.2' - 'Ruby 2.5.5-Rails 4.2' - 'Ruby 2.6.2-Rails 4.2' # 略 filters: branches: only: master
実行したところ、成功!
releaseジョブの中身を確認しても、ちゃんとpushできていた。
#!/bin/bash -eo pipefail gem list --prerelease --all --remote imyou \ | grep -E "^imyou" \ | sed -e "s/^.*(\(.*\)).*\$/\1/" \ | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`" result=$? if [ $result = 0 ]; then bundle exec rake build bundle exec rake release fi imyou 1.3.1 built to pkg/imyou-1.3.1.gem. imyou 1.3.1 built to pkg/imyou-1.3.1.gem. Tagged v1.3.1. Pushed git commits and tags. Pushed imyou 1.3.1 to rubygems.org
課題
ハードコーディングをなんとかしたい(できればorbs化ができそう)- SSH鍵の登録が作業のハードルが高いと思う。API Tokenにできればいいが、rake releaseで使われるのがSSH鍵のほうだろうから、難しそうにも思う。
追記
ハードコーディングは、なんとかなった。
Before
gem list --prerelease --all --remote imyou \ | grep -E "^imyou" \ | sed -e "s/^.*(\(.*\)).*\$/\1/" \ | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`" result=$? if [ $result = 0 ]; then bundle exec rake build bundle exec rake release fi
After
- gemspecファイルを探し出す
ruby -e
内で*.gemspecをロードして、gem名とバージョンを出力して変数に入れる- ハードコーディングしていた箇所をそれらの変数で置き換える
set +e filename=$(for n in *; do printf '%s\n' "$n"; done | grep gemspec) gem_name=`ruby -e "require 'rubygems'; spec = Gem::Specification::load('${filename}'); puts spec.name"` gem_version=`ruby -e "require 'rubygems'; spec = Gem::Specification::load('${filename}'); puts spec.version"` gem list --prerelease --all --remote $gem_name \ | grep -E "^${gem_name}" \ | sed -e "s/^.*(\(.*\)).*\$/\1/" \ | grep -q -v $gem_version result=$? if [ $result = 0 ]; then bundle exec rake build bundle exec rake release fi
ということで、これをOrb化したいなぁ〜。