コマンドラインツールのビルド〜配布をBitbucket Pipelinesで完結させる

アドベントカレンダー駆動開発として、つくりたいと思っていたツール [admin\-mate\-cli](https://bitbucket.org/i05/admin-mate-cli/overview) を作成しました。その際にBitbucketで完結してビルドから配布まで行えるように工夫したので知見をまとめてみます。

ツール自体はGoで開発しましたが [`bitbucket-pipelines.yml`](https://bitbucket.org/i05/admin-mate-cli/src/7cd93ba2d21d98755e0a9a27fcc25429a35dd41b/bitbucket-pipelines.yml?at=v0.1.0&fileviewer=file-view-default) で指定するDockerのイメージを切り替えれば他言語でも応用可能です。

## 概要

次のようなワークフローを想定しています。

* **各開発者**: ローカルでは `make lint build test` で開発を進める
    * コーディング規約の担保
    * ビルド
    * テスト
* **Pipelines - default**: プッシュするとローカルと同じコマンドを実行してくれる
* **Pipelines - tag - v\***: プッシュされたタグが `v1.0.0` などの形式だった場合にリリース作業をしてくれる
* **Pipelines - custom - manual-release**: 任意のコミットに対しても手動リリースが行える


## Pipelinesのベストプラクティス

すべての出発点となる [`bitbucket-pipelines.yml`](https://bitbucket.org/i05/admin-mate-cli/src/7cd93ba2d21d98755e0a9a27fcc25429a35dd41b/bitbucket-pipelines.yml?at=v0.1.0&fileviewer=file-view-default) で工夫できる点を紹介します。

* オンラインの [Validator](https://bitbucket-pipelines.prod.public.atl-paas.net/validator) を利用して記法の検査を行う
* コマンド類はシェルスクリプトとして書き出しておく
* ランタイム環境の依存を増やさずに Docker コマンドですませる
    * 例: [`docker run --rm -v $(pwd):/workdir giantswarm/yamllint ./*.yml`](https://bitbucket.org/i05/admin-mate-cli/src/7cd93ba2d21d98755e0a9a27fcc25429a35dd41b/Makefile?at=v0.1.0&fileviewer=file-view-default#Makefile-50)

```bitbucket-pipelines.yml
image: golang:1.11.2-alpine3.7

pipelines:
  default:
    - step:
        script:
          - apk add build-base git bash
          - ./script/ci.sh init
        caches:
          - docker
          - apk
        services:
          - docker
# ...

definitions:
  caches:
    apk: /etc/apk/cache
```

設定項目について詳しくはドキュメント [Configure bitbucket\-pipelines\.yml](https://confluence.atlassian.com/bitbucket/configure-bitbucket-pipelines-yml-792298910.html) も参照してみてください。


## Pipelinesからリリースを行う

次のようなスクリプトでリポジトリ上からビルド済みのファイルをBitbucketにアップロードできます。

* [`BITBUCKET_TAG`](https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html): Pipelinesのデフォルト環境変数
* [`BITBUCKET_COMMIT`](https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html): Pipelinesのデフォルト環境変数
* [`BB_AUTH_STRING`](https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html): 設定画面にて保存した環境変数（APIの認証情報）

```distribution.sh
API_ENDPOINT="https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads"
FILE_SUFFIX="$(echo ${BITBUCKET_TAG:-$BITBUCKET_COMMIT} | sed -e 's#\.#-#g')"

for FILE_PATH in $(find . -type f ! -name '.*'); do
  FILENAME="$(basename ${FILE_PATH})"
  ARTIFACT="./${BITBUCKET_BUILD_NUMBER}_${FILENAME}_${FILE_SUFFIX}.zip"
  zip -r "${ARTIFACT}" "${FILE_PATH}"
  curl -LX POST --user "${BB_AUTH_STRING}" "${API_ENDPOINT}" --form files=@"${ARTIFACT}"
done
```

配布後にはリポジトリのサイドバー "Downloads" から公開したファイルがダウンロードできるようになります。

[<img width="1792" alt="Screenshot 2018-12-15 21.42.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/1654/908de9a8-a2ee-4186-9ca7-2ae0c32e1352.png">](https://bitbucket.org/i05/admin-mate-cli/downloads/)

また、ローカルでGitのタグ付けをしてからプッシュすると自動でリリースまで行うようにもできます（具体的には`./script/ci.sh release` にてビルド完了後に `distribution.sh` を実行している）。

```bitbucket-pipelines.yml
  tags:
    v*:
      - step:
          name: New version release
          script:
            - apk add build-base git bash zip curl
            - ./script/ci.sh release
          caches:
            - docker
            - apk
          artifacts:
            - pkg/**
          services:
            - docker
```

あとは検証用に手動リリースが行えるようにしておくと便利です。

[<img width="1792" alt="Screenshot 2018-12-15 22.06.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/1654/b0d6c4b3-1af6-ffc3-6ad2-e42c46ab9a7b.png">](https://bitbucket.org/i05/admin-mate-cli/commits/7cd93ba2d21d98755e0a9a27fcc25429a35dd41b?at=v0.1.0)

```bitbucket-pipelines.yml
  custom:
    manual-release:
      - step:
          script:
            - apk add build-base git bash zip curl
            - ./script/ci.sh release
          caches:
            - docker
            - apk
          artifacts:
            - pkg/**
          services:
            - docker
```


## 所感

他の多くのCIサービスと異なり、PipelinesはBitbucketに組み込みなので1つのサイトで完結していて動作も素早いのが気に入っています。
また、コンテナ時代に誕生したCIサービスだけあって `services` で指定するだけでDockerを使えるのも嬉しいです。今回は行っていませんが `Dockerfile` 自体を [github.com/hadolint/hadolint](https://github.com/hadolint/hadolint) で検証してビルドして [Docker Hub](https://hub.docker.com/) に公開する…といったワークフローなども可能です。