patorashのブログ

方向性はまだない

CircleCIのテストが通ったらrake releaseする

前回の記事で、TravisCIからCircleCIに移行したことを書いた。

patorash.hatenablog.com

今回は、テストが通ったら自動的にrake releaseする仕組みを作ったので、それについて書く。 ただし、今回のやつは結構面倒であった。

gemをリリースするorbsは既にある

実はもう既にある模様だが、なんとなく実装が好きになれなかった。

https://circleci.com/orbs/registry/orb/doximity/gem-publisher

  • gem/nameとgem/versionというファイルにgemの名前とバージョン情報を書かなければならないこと
  • rake releaseを使わずにgemをrubygemsにpushしていること
  • たぶん、gemの名前が部分一致してしまうものだと機能しない

そこで、とりあえず自分でやってみることにした。が、実装は参考にさせてもらった。

実装

先に実装を載せておく。結構面倒です。何をやっているのかを書いていく。

  1. チェックアウト
  2. GitHubにアクセスするためのSSH鍵の登録
  3. bundle install
  4. RubygemsにgemをリリースするためのAPI KEYを設定
  5. gitのセットアップ(githubにタグを打つために必要)
  6. 同バージョンがまだリリースされていなければ、rake releaseする
  7. 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鍵が必要なため、以下のサイトを参考にして作業した。

sue445.hatenablog.com

qiita.com

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 にアクセスして、公開鍵を登録する。

f:id:patorash:20190327155104p:plain

id_rsa_circleci.pubの内容を貼り付けて保存する。このとき、Allow write accessにチェックを入れるのを忘れないこと。 保存できたら、もともと登録されていたであろうread onlyの鍵を削除しておく。

CircleCIに秘密鍵を登録

次に、https://circleci.com/gh/[username]/[reponame]/edit#ssh にアクセスして、秘密鍵を登録する。

f:id:patorash:20190327155755p:plain

id_rsa_circleciの内容を貼り付けて保存する。

その後、https://circleci.com/gh/[username]/[reponame]/edit#checkout にアクセスして、デプロイキーを削除する(これがread onlyのやつの秘密鍵)。

RubygemsAPI KEYを環境変数に保存

RubygemsAPI KEYはRubygemsにログインしてプロフィールの編集画面にいったら書いてある。

f:id:patorash:20190327160520p:plain

この値を、CircleCIの環境変数に登録する。変数名は、RUBYGEMS_API_KEYにする。

f:id:patorash:20190327160712p:plain

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

実行したところ、成功!

f:id:patorash:20190327162845p:plain

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

  1. gemspecファイルを探し出す
  2. ruby -e内で*.gemspecをロードして、gem名とバージョンを出力して変数に入れる
  3. ハードコーディングしていた箇所をそれらの変数で置き換える
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化したいなぁ〜。