gored: golangプロジェクトをローカルでもCIでもビルド/リリースできるようにするための環境整備ツール

golangで作ったツールをリリースするときにはだいたい以下のような作業をすることになります。

- 依存ライブラリをダウンロード([golang/dep](https://github.com/golang/dep)や[Masterminds/glide](https://github.com/Masterminds/glide)などが便利)
- 様々なプラットフォーム用にビルド([mitchellh/gox](https://github.com/mitchellh/gox) / [laher/goxc](https://github.com/laher/goxc) / [Songmu/goxz](https://github.com/Songmu/goxz))
- ビルドしたバイナリを圧縮([laher/goxc](https://github.com/laher/goxc) / [Songmu/goxz](https://github.com/Songmu/goxz))
- バージョンをインクリメント([motemen/gobump](https://github.com/motemen/gobump))
- CHANGELOGをアップデート([Songmu/ghch](https://github.com/Songmu/ghch))
- GitHub Releaseへバイナリをアップロード([tcnksm/ghr](https://github.com/tcnksm/ghr))

先人の様々なツールによってgolang製ツールのリリースが成り立っていることがわかりますね。
このような環境セットアップやリリースを誰でも簡単に行えるようにするため、多くのgolangプロジェクトではMakefileが書かれます。(と、[みんなのGo言語](http://gihyo.jp/book/2016/978-4-7741-8392-3)に書かれていました)
また、最近のCIサービスではDocker Imageを利用することが多いため、一連の処理がコンテナ上で動くようになっていると、ローカルとCIで同じ環境を利用できて便利です。
そこで、既存のgolangプロジェクトに、コンテナ上でのビルド/リリース環境を簡単に準備するためのDocker Imageを作りました。

[mpppk/gored: release engineering (via docker) tool for golang project](https://github.com/mpppk/gored)

## 使い方
以下のコマンドを実行します。

```release.sh
$ cd gopath/src/github.com/yourname/yourrepo
$ docker run -v $(pwd):/init mpppk/gored     # リリースに必要なファイル群を生成
$ docker-compose run bump-and-commit         # バージョンとCHANGELOGを更新してgit push
$ docker-compose run release                 # ビルドしたバイナリをGitHub reelasesへアップロード
```

実行したあとにリポジトリを見てみると、releaseされていることがわかります。
[![Image](https://gyazo.com/777ecc0fcf2dcd6aa91967f0347ee459/thumb/1000)](https://gyazo.com/777ecc0fcf2dcd6aa91967f0347ee459)

バージョンとCHANGELOGも更新されています。
[![Image](https://gyazo.com/0e5f0377caed3747c61401ab00969089/thumb/1000)](https://gyazo.com/0e5f0377caed3747c61401ab00969089)

すごい！かんたん！！

## 何が起きているのか
まず1行目の`docker run -v $(pwd):/init mpppk/gored`は、実行すると以下のファイルが生成されます。

- `docker-compose.yml`
- `Makefile`
- `.circleci/config.yml`

(既にこれらのファイルが存在する場合は`gored`と言うsuffix付きで生成します)

デフォルトではビルドパスやバージョン番号が含まれたgoファイルのパスはカレントディレクトリを参照します。変更する場合は`--build-path`や`--version-path`を利用してください。

```Shell
$ docker run -v $(pwd):/init mpppk/gored gored init --build-path [build path] --version-path [version path]
```

毎回これを打つのがめんどくさい場合は、シングルバイナリ版をお使い頂くと多少マシになります。

```Shell
$ go get github.com/mpppk/gored
$ gored init --build-path [build path] --version-path [version path]
```

次に`docker-compose run bump-and-commit`と`release`です。
実際とは少し違いますが、生成された`docker-compose.yml`の`bump-and-commit`と`release`はそれぞれ以下のような内容になっています。

```docker-compose.yml
bump-and-commit: &bump-and-commit
  image: mpppk/gored
  volumes:
    - $PWD:/go/src/github.com/yourname/yourrepo
  environment:
    GITHUB_TOKEN: $GITHUB_TOKEN
  working_dir: /go/src/github.com/yourname/yourrepo
  command: make bump-and-commit -f Makefile
release:
  <<: *bump-and-commit
  command: make release -f Makefile
```

それぞれ`Makefile`の同名ターゲットを実行していますね。
では`Makefile`を見てみます。

```Makefile
(略)
deps:
    dep ensure
 
bump-and-commit: deps test
    gobump patch -w $(BUILD_PATH)
    ghch -w -N v`gobump show -r $(BUILD_PATH)`
    git add CHANGELOG.md
    git commit -am "Checking in changes prior to tagging of version v`gobump show -r $(BUILD_PATH)` [skip ci]"
    git tag `gobump show -r $(BUILD_PATH)`
    git push "https://$(GITHUB_TOKEN)@github.com/$(REPO_OWNER)/$(REPO_NAME)" HEAD:master
 
crossbuild: deps test
    goxz -pv=v`gobump show -r $(BUILD_PATH)` -d=./dist/v`gobump show -r $(BUILD_PATH)` $(BUILD_PATH)
 
release: crossbuild
    ghr --username $(REPO_OWNER) `gobump show -r $(BUILD_PATH)` dist/v`gobump show -r $(BUILD_PATH)`
(略)
```

`bump-and-commit`では、以下の処理が行われます。

1. `deps`ターゲットで依存ライブラリをダウンロード
1. gobumpでバージョンをpatchインクリメントして`git add`
    - 例えばソースコード中の`const version = "0.0.1"`が`const version = "0.0.2"`に書き換わります。
1. ghchでCHANGELOG.mdを更新して`git add`
1. `git commit`してバージョンタグを打って`git push`

`release`では以下の処理が行われます。

1. `crossbuild`ターゲットで、goxzによりwindows, mac, linux用のバイナリを生成
1. ghrでreleaseにバイナリをアップロード

以上の処理が`docker-compose.yml`によりカレントディレクトリをマウントした状態で`mpppk/gored`コンテナ上で実行されます。
`mpppk/gored`イメージには既に`gobump`や`ghr`などのツールがインストール済みなので、別途インストールする必要はありません。

## CI
上記まででコンテナ上でのリリースができるようになっているため、Docker Imageを利用するCIサービスだと大体自動リリースもシュッと実現できると思います。
goredではCircle CIを特に優遇しており、設定ファイルである`.circleci/config.yml`を一緒に生成するので、これをコミットしておくと次からはPRのマージ時に自動でリリースされます。

```config.yml
version: 2
jobs:
  build: &build
    docker:
      - image: mpppk/gored
    working_directory: /go/src/github.com/yourname/yourproject
    steps:
      - checkout
      - run: make test
  release:
    <<: *build
    steps:
      - checkout
      - run: make bump-and-commit
      - run: make release
workflows:
  version: 2
  build_and_release:
    jobs:
      - build
      - release:
          requires:
            - build
          filters:
            branches:
              only: master
```

こんな感じで、masterにpushされた時だけrelease jobが走ります。
注意点として、release時のcommit messageの末尾には必ず`[skip ci]`という文字列を含めておいてください。
これがないとrelease時のpushをtriggerにまたCircle CIが走り始めるので、無限にリリースが行われ続けます。(これに気付かずうっかり`v0.0.1`から`v0.0.804`まで一瞬でリリースされました)

## workflowの変更
このようにリリース作業に必要なファイルを自動生成してくれるgoredですが、実際にはworkflowを一部変更したいことがあると思います。この場合は生成されたMakefileを編集してください。
例えばデフォルトのMakefileでは依存ライブラリの解決にdepを使いますが、代わりにglideを使いたい場合は、`deps`ターゲットを以下のように変更してください。

```Makefile
(略)
deps:
    glide install
(略)
```

その他、`mpppk/gored`イメージに含まれていないツールを使いたい場合、とりあえずMakefileに書いちゃってもいいですが、毎回`go get`するのが気になる場合は以下のような自前のDockerfileを書いた方が良いかもしれません。

```Makefile
(略)
setup:
    go get ${u} github.com/golang/dep/cmd/dep
    ...(追加したいpackageを書く)
(略)
```

```Dockerfile
FROM golang:1.9.2 AS builder
COPY Makefile /go/src/github.com/yourname/yourrepo/Makefile
WORKDIR /go/src/github.com/yourname/yourrepo
RUN make setup
 
FROM circleci/golang:1.9.2
COPY --from=builder /go/bin/* /go/bin/
RUN git config --global user.email "gored-bot@dammy.com"
RUN git config --global user.name  "gored-bot"
```

というわけで、お手軽に自動リリース環境を構築する[gored](https://github.com/mpppk/gored)の紹介でした。ご興味あれば使ってみてください！

## 参考
- [Go プロジェクトのバージョニングとリリースを CI で自動化する - 詩と創作・思索のひろば](http://motemen.hatenablog.com/entry/2015/08/go-auto-gobump-ci)
- [Kazuho's Weblog: CI 用 Docker イメージ作成におけるベストプラクティス](http://blog.kazuhooku.com/2017/12/ci-docker.html)
- [Goツールのクロスビルドとパッケージングのためのgoxzというツールを作った](http://www.songmu.jp/riji/)
- [高速に自作パッケージをGithubにリリースするghrというツールをつくった | SOTA](https://deeeet.com/writing/2014/07/29/ghr/)