Shippableでgh-pagesにdeploy

# 要約

CIサービスであるShippableにおいて、Build成果物を別のbranchのcommitしてSSH経由でpushすることに成功した

# 背景

JavaScript/CSSのminifyであったり、sass/lessのコンパイルやJavaScriptのトランスコンパイル、GitBook等によるHTMLの生成など、近代のWeb開発はビルドフェーズがついて回る。

ところでGithubは、Github Pagesと呼ばれるサービスを提供している。これは
``https://github.com/<user or organization name>/<repo name>``
の``gh-pages`` branchもしくは``master`` branchの``/docs`` ディレクトリを
``https://<user or organization name>/github.io/<repo name>``
(注:``<repo name>``が``<user or organization name>.github.io``の場合は``https://<user or organization name>/github.io``)
にWeb ホスティングするサービスである。

ここで、``master`` branchのビルド結果を``gh-pages`` branchにDeployしたいという需要がある。

# 先行例

@azu 氏を中心として、JavaScriptの入門書をGitBookで作成しているプロジェクトがある。

https://github.com/asciidwango/js-primer

ここでは、CIサービスであるTravis CIを利用して``gh-pages`` branchへのdeployを行っている。

具体的には

- [travis-scripts](https://github.com/alrra/travis-scripts)
- [travis-after-all](https://github.com/alrra/travis-after-all)

という2つのnpm packageを利用してSSH経由でDeployしている。

# 問題点

## Travis CIを使いたくない

何かの都合ですでにTravis CIは使っていて別のCIでDeployしたいということはままある。

## Deployのためにnpmの依存やnpm scriptsを増やしたくない

個人的にlocalで動かせないものはあんまnpm使いたくない。というかどうせshell scripts書く時点で環境依存なんだからわざわざnpm scriptsなんて挟まないでshell scriptsで完結したい

# 解決策

Shippableを使ってみる

https://app.shippable.com/

ShippableもCIサービスで、docker image使えたりpipeline組めたりもするらしい。

## 全体像
### CI実行前
1. local machineで公開鍵と秘密鍵を生成
2. Shippableに公開鍵と秘密鍵を登録
3. Githubに公開鍵を登録

### CI実行時
1. CIによってtarget branchがclone/checkoutされる
2. buildする
3. SSH keyをloadして別途Repogitryをclone、``gh-pages``をcheckoutする
4. 2の結果を3でcloneした場所にcopyする
5. commitする。この時``[skip ci]``と記述しないとdeployした``gh-pages``に対するCIが動いてしまう
6. SSH keyをloadしてpushする

# 詳細

## local machineで公開鍵と秘密鍵を生成  

これは特に難しくない。

```bash
$ssh-keygen -t rsa -b 4096 -C "yume-wikijp@live.jp" -f github_deploy_key -N ''
```

とするとカレントディレクトリに

- ``github_deploy_key``
- ``github_deploy_key.pub``

の2つができる。``github_deploy_key``は秘密鍵なので取り扱いには注意する。

Windowsの場合はmsys2を入れて

```bash
$pacman -S openssh
```

とかすると、``ssh-keygen``が使える。

メールアドレス(``-C``)や鍵の名前(``-f``)は適宜読み替えて。

信頼できるエントロピー源のないVPSや一部の仮想環境では鍵を作らないほうがいい。

## Shippableに公開鍵と秘密鍵を登録

1. ShippableのDashboardに行きsettingsを開く  
![Shippable's Dashboard](https://cloud.githubusercontent.com/assets/10869046/22623533/c9805c34-eba1-11e6-9f82-96db201c9b40.png)
2. Integrations へ行く  
![Shippable Option](https://cloud.githubusercontent.com/assets/10869046/22623552/55261ff8-eba2-11e6-9c3a-ce01c6d4c835.png)
3. Add Integrationをクリック  
![Shippable integrations](https://cloud.githubusercontent.com/assets/10869046/22623556/76155774-eba2-11e6-80e6-a524790a1240.png)
4. ここではintegrationの名前は``shippable2githubssh``とした  
![Integration name](https://cloud.githubusercontent.com/assets/10869046/22623559/915a3bbc-eba2-11e6-976b-2db54d923b27.png)
5. Account Integrations -> Add integration  
![Add integration](https://cloud.githubusercontent.com/assets/10869046/22623561/ae4d11ae-eba2-11e6-91d6-aed9a1208730.png)
6. SSH Keyを選択  
![Account integration](https://cloud.githubusercontent.com/assets/10869046/22623567/d1e596ae-eba2-11e6-9c51-5f0f23c5b1e3.png)
7. ここでは名前は``shippable2githubssh``にした。公開鍵と秘密鍵を貼り付ける。 Saveをクリックする。 
![Create SSH Key Integration](https://cloud.githubusercontent.com/assets/10869046/22623570/fa381fa0-eba2-11e6-8ff9-f585546fe1d7.png)
8. Saveをクリックする  
![Add Integration - Save](https://cloud.githubusercontent.com/assets/10869046/22623575/2444a160-eba3-11e6-8964-72526ef233a4.png)
9. 正しく登録されている  
![Subscription intehrations](https://cloud.githubusercontent.com/assets/10869046/22623579/38f4921e-eba3-11e6-9835-de2ce9ede1b8.png)

## Githubに公開鍵を登録

1. GithubのRepogitryを開き、Setting -> Deploy keys -> Add deploy Keyをクリック  
![Open Deploy keys](https://cloud.githubusercontent.com/assets/10869046/22623580/5b818936-eba3-11e6-8d88-d887a5f735e4.png)
2. **公開鍵**を貼り付け、``Allow write accsess``にチェックを付ける。Add keyをクリック  
![Register Deploy key](https://cloud.githubusercontent.com/assets/10869046/22623589/7e36778e-eba3-11e6-9afb-d7e81893444f.png)
3. 正しく登録された。  
![Registerd Deploy key](https://cloud.githubusercontent.com/assets/10869046/22623593/9bf487d4-eba3-11e6-83d1-9ae7d7a2b7d0.png)

## ymlを書く

### intagrationを有効にする

```yaml:shippable.yml
integrations:
  key:
    - integrationName: shippable2githubssh
      type: ssh-key
```

を追記する。すると実行時に

```bash
$ls -l /tmp/ssh
total 12
-rw------- 1 root root 1680 Feb 10 23:53 00_sub
-rw------- 1 root root 1676 Feb 10 23:53 01_deploy
-rw------- 1 root root 3243 Feb 10 23:53 shippable2githubssh
```

SSH keyが``/tmp/ssh``にある。

注意点として、**[公開レポジトリの場合、Pull Requestのビルド時にはintagrationは無効化される](https://github.com/Shippable/support/issues/3347#issuecomment-277561276)**。じゃないとだれでもDeployをできることになっちゃうしね。

### Deployの条件

- ビルドが成功
- 任意のbranch(今回は``yumetodo/master``というbranch)に対する実行時
- Pull Request Buildではない(上述のとおり)

をすべて満たしたとき、Deployを実行するには

```yaml:shippable.yml
build:
  on_success:
    - if [ "$BRANCH" == "yumetodo/master" ] && [ "${IS_PULL_REQUEST}" != "true" ]; then ./tools/shippable_deploy.sh; fi
```

のようにする。

``BRANCH``や``IS_PULL_REQUEST``はShippableで定義されている環境変数で、一覧は
[Using Environment Variables for Continuous Integration - Shippable Docs](http://docs.shippable.com/ci/advancedOptions/environmentVariables/)
で見られる。

## Deploy Script

ymlにDeployの手順を全部書くのはだるいので普通にshell Scriptを書いて呼び出す。

先行例で上げたjs-primerの場合は``gh-pages``のcommitを全部消してからcommitしているが、今回はそうせずに普通にcommitする。

```bash:./tools/shippable_deploy.sh
#!/bin/bash -eu
echo "ll /tmp/ssh"
ls -l /tmp/ssh
echo "ll /dev/shm"
ls -l /dev/shm

echo "clone taget repogitry..."
ssh-agent bash -c 'ssh-add /tmp/ssh/shippable2githubssh; git clone -b gh-pages git@github.com:yumetodo/Hatena-Blog-Themes.git /dev/shm/Hatena-Blog-Themes'
bash -c 'cd /dev/shm/Hatena-Blog-Themes; git status;'
cd $SHIPPABLE_BUILD_DIR

echo "copy minified result..."
cp -r bin /dev/shm/Hatena-Blog-Themes
cd /dev/shm/Hatena-Blog-Themes
git config user.name "yumetodo"
git config user.email "yume-wikijp@live.jp"
git config push.default simple
git add .

echo "commit changes..."
git commit -m "Deploy minified css/js [skip ci]" &&:
errno=$? # bind error code
if [ $errno != 0 ]; then
    echo "git commit exit with ${errno}"
    exit 0 # nothing to commit means nothing to deploy.
fi

echo "push commit..."
ssh-agent bash -c 'ssh-add /tmp/ssh/shippable2githubssh; git push'

echo "finish deploy."
cd $SHIPPABLE_BUILD_DIR
```

まあ特に難しいことはしていない。

```bash
cp -r bin /dev/shm/Hatena-Blog-Themes
```

の部分がビルド成果物をdeploy用にcloneしたディレクトリへコピーしている部分だ。適宜書き換えれば良い。

SSH keyを呼び出すのは``ssh-add``コマンドを使っている。
ssh-agentについては
[ssh-agentの使い方 - Qiita](http://qiita.com/isaoshimizu/items/84ac5a0b1d42b9d355cf)
が詳しい。

当たり前だが、SSH Keyが必要なのはcloneする時とpushする時のみで、ほかは必要ない。

冒頭の

```bash
#!/bin/bash -eu
```

が大事で、``-eu``がないとscriptの途中でコケたときにexit codeがscript呼び出し側に伝わらないだけではなく、実行が中断されない。この辺については

- [シェルスクリプトを書くときはset -euしておく - Qiita](http://qiita.com/youcune/items/fcfb4ad3d7c1edf9dc96)
- [`set -e` しているときにコマンドの戻り値を得る - Qiita](http://qiita.com/ngyuki/items/aefd47700a9522fada75)

が詳しい。

commit logを消してからcommitとしてない都合上、commitするような変更がない可能性がある。そのときにエラーで終了されるとShippableの実行ステータスが失敗になってしまうので握りつぶす必要がある。

```bash
git commit -m "Deploy minified css/js [skip ci]" &&:
errno=$? # bind error code
if [ $errno != 0 ]; then
    echo "git commit exit with ${errno}"
    exit 0 # nothing to commit means nothing to deploy.
fi
```

またcommit messageには忘れずに``[skip ci]``を書いておきましょう。

# できたもの

https://github.com/yumetodo/Hatena-Blog-Themes

ほい。このレポジトリがなんなのかについては
[FC2からはてなブログに移行した - yumetodoの旅とプログラミングとかの記録](http://yumetodo.hateblo.jp/entry/2017/02/12/165033)
を参照。

# 最後に

>公開レポジトリの場合、Pull Requestのビルド時にはintagrationは無効化される

というのを知らずになんでやーーーとなった挙句、Shippableのサポートに泣きついたのはここだけの秘密
[found SSH Key integration but SSH key not found in /tmp/ssh · Issue #3347 · Shippable/support](https://github.com/Shippable/support/issues/3347)

# 追記

https://github.com/Shippable/docsv2/pull/716

SSH key integrationを使うときに新たにkeyの場所を取れる環境変数が追加された。

# License

[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja)

<a href="https://commons.wikimedia.org/wiki/File:CC-BY_icon.svg#/media/File:CC-BY_icon.svg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/CC-BY_icon.svg/1200px-CC-BY_icon.svg.png" alt="CC-BY icon.svg" width="88px"></a>