Travis CIで全テスト通ったら勝手にリリースタグを打って再度ビルドを走らせる方法

Travis CIでテストを並列実行する場合に、全テストがパスしたときだけリリースタグを打つ方法を紹介します。

これは普通に実現できそうな内容に見えるかもしれませんが、これまでTravis CIではタスクの依存関係を定義することができませんでした。つまり、[Build Matrix](https://docs.travis-ci.com/user/customizing-the-build#Build-Matrix)の仕組みを使って複数のテストジョブを並列実行した場合に、全テストの後続タスクとしてデプロイを実行するようなことは不可能でした[^1]。

今回、Travis CIの新機能である[Build Stages](https://docs.travis-ci.com/user/build-stages/)[^2]を利用すれば要求を満たせることに気付いたので、実際に試してみたというわけです。

[^1]: テストを1並列で行うのであれば「テスト成功時のみデプロイ」は以前から実現可能です。`deploy:`セクションに書いた内容は`script:`セクションのテストやビルドが成功したときだけ実行されます。
[^2]: 2017年10月時点ではまだベータ版です。機能としてキャンセルされる可能性もゼロではないのでご注意ください。

# `.travis.yml`の記述

既存の全テストジョブが成功したらGitでタグを打つための手順を紹介します。まずは、既存の`.travis.yml`の末尾に次のように書いてください。

```yaml:.travis.yml
jobs:
  include:
    -
      if: branch = master
      stage: Push release tag
      script: ./git-tag.sh
      deploy: []
```

このようにすると、`matrix:`で定義してある既存のテストは全て「test」ステージのジョブになり、その後続タスクとして「push release tag」ステージが実行されることになります。このステージでは`git-tag.sh`が実行されます。

# リリースタグを打つスクリプト

この`git-tag.sh`の中身は次のようなものです。

```bash:git-tag.sh
#! /usr/bin/env bash

# この時点で打つべきタグを決定して$TAGに代入しておく
# タグの情報源はgitのコミットログやプロジェクト内の設定ファイルなどが考えられる

TAG=${PKG_VERSION}-${PKG_RELEASE}
if [[ -n "$(git tag -l ${TAG})" ]]; then
    echo "Already exist tag: ${TAG}"
    echo "Do nothing."
else
    git config --global "url.git@github.com:.pushinsteadof" "https://github.com/"
    git config --global user.name "Travis CI"
    git config --global user.email "travis@example.com"
    mkdir -p $HOME/.ssh
    chmod 700 $HOME/.ssh
    openssl aes-256-cbc -K $encrypted_****_key -iv $encrypted_****_iv -in id_ecdsa.enc -out $HOME/.ssh/id_ecdsa -d
    chmod 600 $HOME/.ssh/id_ecdsa
    echo git tag $TAG
    git tag ${TAG}
    echo git push origin $TAG
    git push origin ${TAG}
fi
```

見てわかる通り、`git tag`して`git push`しています。pushの権限を与えるのにGitHubのdeploy keyの仕組みを利用しています。

# deploy keyの準備

GitHubには「deploy key」という仕組みがあり、特定のプロジェクトに紐付くSSH鍵を登録することができます。deploy keyはGitHubに登録してある既存の鍵は使えないので、デプロイ専用の鍵ペアを新規作成して利用します。

```
$ ssh-keygen -t ecdsa -f /tmp/id_ecdsa
$ travis encrypt-file /tmp/id_ecdsa
```

ssh-keygenで指定するパスフレーズは空文字列にします。つまり、ここで作られる秘密鍵はパスフレーズで守られていないことになりますが、`travis encrypt-file`により公開鍵暗号で暗号化されますので理屈上は安全です。

`travis encrypt-file`を実行すると`openssl`の行が表示されますので、これを`.travis.yml`に貼り付けた上で適宜修正します。また、カレントディレクトリに`id_ecdsa.enc`ファイルが作られます[^3]ので、これをGitリポジトリにコミットします。

さらに、対になる公開鍵`id_ecdsa.pub`をGitのWeb管理画面からdeploy keyとして登録します。deploy keyはGitHubのプロジェクトページの「Settings」「Deploy keys」から登録できます。今回はtagをpushしたいので、「Allow write access」のチェックボックスをオンにする必要があります。

[^3]: ECDSA鍵を使っているのは個人的な趣味です。RSA鍵を使っても問題はありません。

# 結果

上記の設定を行うと、masterへのコミットに対して、たとえば下記のように4並列のジョブが全部パスした後でタグだけを打つジョブが走ることになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/46856/7d77907e-0104-8391-e739-3112045f9ebb.png)

設定にもよりますが、このようにタグを打つと再度Travis CI上でビルドが走るはずです。くれぐれも無限ループしないよう気をつけてください。このプロジェクトではタグを打ったときだけGitHub Releaseにデプロイするようにしてあるので、この仕組みにより手動でタグを打つ一手間が減ったことになります。

# 補足：こんな面倒なことをする理由

今回実現したかった内容は「masterでのビルドが通ったらGitHub Release上に生成物をデプロイしたい」というものです。

ただし、GitHub Releaseはその仕組み上、必ず対応するタグが必要になります。タグに対応するビルドが走るのは必須ということになります。

一方で、ビルドが通るかわらかないうちからタグを打つわけにいかないので、masterでのビルドも必須です。そこで、本稿で説明したように必ず2周ビルドする処理を組むことにしました。リソースの無駄遣いのような気もしますが、他にどうしようもないと思います。