SlackからAPKを配布する

この記事は[Android Advent Calendar 2016](http://qiita.com/advent-calendar/2016/android)の16日目の投稿になります。

# 概要

社内QAを行う場合などに[Fabric Beta](https://docs.fabric.io/android/beta/overview.html)でAPKを配布しているのですが、その作業を[Slack](https://slack.com/) + [Hubot](https://hubot.github.com/) + [Wercker](http://www.wercker.com/)で自動化してみます。

# APKが配布されるまでの流れ

流れとしては以下のようになります。

1. Slackでコマンドを打つ
2. Hubotが反応
3. WerckerのAPIを叩きビルドを開始
4. APKをFabric Betaで配布

![advent_2016_1.png](https://qiita-image-store.s3.amazonaws.com/0/43438/0a4b5a2c-fb02-d8b1-766d-85d42d24d892.png "advent_2016_1.png")

# Slackでコマンドを打つ

まず、APKの配布を実行するコマンドを決めます。QAのためにAPKを配布する場合を想定し、コマンドを`android qa`とします。任意のメッセージをリリースノートとして指定したいですが、一旦脇に置いておきます。

実際にAPK配布を実行したい場合、以下のようにSlackに入力します。

![advent_2016_2.png](https://qiita-image-store.s3.amazonaws.com/0/43438/6cc38d3c-2432-30fe-a2aa-b963b0682dd1.png "advent_2016_2.png")

# Hubotが反応

Slackに`android qa`と入力されたのをHubotで検知します。まずHubotを準備し、Slackと連携させてみましょう。

SlackでHubotを使う方法についてはこちらの記事が参考になります。

- http://qiita.com/misopeso/items/1f418dd02e89234499b3

また、公式のドキュメントはこちらになります。

- https://hubot.github.com/docs/

なお、今回Herokuのコンテナ上で動かすNode.jsのバージョンは`6.9.1`を指定しています。したがって、コードをES2015で書くことができ、Slackで`android qa`と入力された場合`qa start`とログに表示させるコードは以下のようになります。

```javascript:qa.js
module.exports = (robot) => {
  robot.hear(/^android\s+qa$/, (msg) => {
    console.log("qa start");
  });
}
```

Hubotのディレクトリ構成は以下のようになっています。`scripts`以下にコードを配置しましょう。

```bash
.
├── Procfile
├── README.md
├── bin
│   ├── hubot
│   └── hubot.cmd
├── external-scripts.json
├── hubot-scripts.json
├── package.json
└── scripts
    └── qa.js
```

`bin/hubot`を実行するとローカルでHubotの挙動を確認でき、`android qa`と入力すると`qa start`と表示されます。

![advent_2016_3.gif](https://qiita-image-store.s3.amazonaws.com/0/43438/b672ad7b-0263-24ed-db0a-2c0a5529207a.gif "advent_2016_3.gif")


# Wercker APIを叩きビルドを開始

![advent_2016_4.png](https://qiita-image-store.s3.amazonaws.com/0/43438/0c7f2417-a175-d611-feba-e34aaff3cdd0.png "advent_2016_4.png")

Wercker APIの[Trigger a new run](http://devcenter.wercker.com/docs/api/endpoints/runs#trigger-a-run)使用することで任意のタイミングでビルドを開始させることが可能です。CircleCIであれば[Trigger a new Build](https://circleci.com/docs/api/#new-build)を使うと同じことができます。

cURLでAPIを叩く場合以下のようになります。`TOKEN`は[https://app.wercker.com/profile/tokens](https://app.wercker.com/profile/tokens)から取得できます。

```bash
curl -X POST -H "Content-Type: application/json; charset=utf-8"
-H "Authorization: Bearer YOUR_TOKEN"
-d '{
	"pipelineId": "PIPELINE_ID"
}' https://app.wercker.com/api/v3/runs
```

ここで、`PIPELINE_ID`はWerckerのWorkflowsを構成するPipelineのIDとなります。WorkflowsはPipelineを繋げることで作成されます。Workflowsについての詳細は[こちら](http://devcenter.wercker.com/docs/workflows)をご覧ください。

## Wercker APIクライントの利用

HubotからWercker APIを叩く場合、Node.js向けのAPIクライントがあると便利です。今回の用途に丁度いいクライントが無かったので自作してみました。

https://github.com/horie1024/wercker-client

wercker-clientを使用して先程のcURLでのリクエストを以下のように置き換えます。

```javascript
const Wercker = require("wercker-client").default;
const wercker = new Wercker({token: "YOUR_TOKEN"});

wercker.Runs.triggerNewRun({
  pipelineId: "PIPELINE_ID",
}).then((res) => {
  console.log(res);
});
```

これを先程のHubotスクリプトと組み合わせると以下のようになり、これで`android qa`とSlackで入力するとWerckerでビルドが開始されるようになります。

```javascript:qa.js
const Wercker = require("wercker-client").default;
const wercker = new Wercker({token: "YOUR_TOKEN"});

module.exports = (robot) => {
  robot.hear(/^android\s+qa$/, (msg) => {
    wercker.Runs.triggerNewRun({
      pipelineId: "PIPELINE_ID"
    }).then((res) => {
      console.log(res);
    });
  });
}
```

# APKをFabric Betaで配布

![advent_2016_5.png](https://qiita-image-store.s3.amazonaws.com/0/43438/6b22d85c-d0c7-694f-b4ba-9fc353323aca.png "advent_2016_5.png")

Fabric Betaの設定方法やオプションについては以下の記事でまとめています。

[CLIからBetaでのAPK配布を行う際の設定まとめ](http://qiita.com/Horie1024/items/983d5c11d8ce92edc8a5)

WerckerではWorkflowと呼ばれるビルドパイプラインに沿ってビルドが実行されます。Workflowを設定するのは、まずユニットテストとAPK配布が実行されるPipelineを以下のようにwercker.ymlに定義します。

```yaml:wercker.yml
build:
  steps:
    - script:
        name: Unit test
        code: |
          ./gradlew testDebug

distribute-apk:
  steps:
    - script:
        name: Distribute APK
        code: |
         - ./gradlew assembleDebug crashlyticsUploadDistributionDebug
```

## Pipelineの追加とWorkflowsの作成

wercker.ymlに定義しただけではPipelineとして使用できないのでWeb UIから「Add new pipeline」で追加します。

![advent_2016_6.png](https://qiita-image-store.s3.amazonaws.com/0/43438/2ac90265-0371-62fe-349c-3827c2db4deb.png "advent_2016_6.png")

Nameには任意の識別しやすい名前、YML Pipeline nameにはwercker.ymlに定義したPipelineの名前を指定します。

![advent_2016_7.png](https://qiita-image-store.s3.amazonaws.com/0/43438/b88e9c1f-7b96-de44-5f8c-74122a02cf53.png "advent_2016_7.png")

追加したPipelineをEditorでつなぐことでWorkflowsを作成します。`On branch(es)`には`*`を指定し全てのブランチで実行されるようにします。

![advent_2016_8.png](https://qiita-image-store.s3.amazonaws.com/0/43438/b2ad528e-01ad-cf09-dba6-ea359f90405d.png "advent_2016_8.png")

最終的なWorkflowは以下のようになり、`build` Pipelineの実行が完了後直列に`distribute-apk` Pipelineが実行されます。

![advent_2016_9.png](https://qiita-image-store.s3.amazonaws.com/0/43438/9b03e414-d791-48f5-9692-9c9d13474ab1.png "advent_2016_9.png")

## Workflowの実行の流れ

ここで、WerckerがWorkflowを実行する条件は以下の通りです。

1. VCS(GitHub、Bitbucket)へのPush
2. Hubotからのトリガ

したがって、このままではVCSにプッシュする都度APKの配布が行われてしまいます。

## 環境変数による配布の制御

APKの配布制御するために環境変数`DIST`をWerckerのEnvironmentで定義し、`DIST`が`true`の場合のみAPKを配布するようにします。Werckerで定義する`DIST`のValueは`false`にしておきます。

```yaml:wercker.yml
build:
  steps:
    - script:
        name: Unit test
        code: |
          ./gradlew testDebug

distribute-apk:
  steps:
    - script:
        name: Distribute APK
        code: |
          if [ "$DIST" = "true" ]; then
            ./gradlew assembleDebug crashlyticsUploadDistributionDebug
          fi
```

[Trigger a new run](http://devcenter.wercker.com/docs/api/endpoints/runs#trigger-a-run)は、`envVars`パラメータでビルド実行時の環境変数を上書きすることができます。wercker-clientで`envVars`を指定する場合以下のようにします。

```javascript:qa.js
const Wercker = require("wercker-client").default;
const wercker = new Wercker({token: "YOUR_TOKEN"});

module.exports = (robot) => {
  robot.hear(/^android\s+qa$/, (msg) => {
    wercker.Runs.triggerNewRun({
      pipelineId: "PIPELINE_ID",
      envVars: [{key: "DIST", value: "true"}]
    }).then((res) => {
      console.log(res);
    });
  });
}
```

これでHubotから`distribute-apk` Pipelineをトリガした場合にのみAPKが配布されるようになります。`distribute-apk` Pipelineをトリガの対象にするため、Pipeline IDは`distribute-apk` Pipelineのものを使用します。

Pipeline IDは、Pipeline詳細ページのURLから確認できます。

```bash
https://app.wercker.com/{user_name}/{application_name}/workflows/pipelines/{pipeline_id}
```

# まとめ

SlackからAPKを配布できるようにしてみました。

Werckerのビルドをトリガする部分はHubotに依存していないので他の用途にも使えます。Amazon Dashボタンを押すとAPKを配布することも簡単にできると思うので試してみようと思います。