Slackと連携するHubotをDocker in AWSで構築


##やりたいこと
- Slackと連携できるHubotを立てる
- デプロイ方法は、Github -> CircleCI -> AWSとする
- slackへのAccess Tokenなどは環境変数で渡す（誰でも利用しやすいように）
- scriptsは誰でも追加しやすいように、localレポジトリのをDockerへ持っていくようにする

##まずはHubotに必要なものを揃える

- node.js
- npm

Mac環境ならどちらも`brew install`で簡単に用意できる

YeomanとYeomanの`generator-hubot`を用意する。
`-g`をつけずにbundle的なのりでローカルにインストールしたかったが、実行にあたってのパス設定が面倒そうなので諦めた。

```zsh
% npm install -g yo generator-hubot
% yo hubot
% bin/hubot
```

これでhubotとの対話インターフェースが立ち上がる。

- `scripts`ディレクトリ以下にスクリプトを置くと、機能追加ができる。
- `hubot-scripts`ディレクトリ以下には、`hubot-scripts.json`に定義したscriptsがインストールされる。利用できるscriptsは[こちら](https://github.com/hubot-scripts)
- `package.json`に利用したいthird-party npm packageを追記すると、external-scripts.jsonに値が追加される。
- `ERROR hubot-heroku-alive included, but missing HUBOT_HEROKU_KEEPALIVE_URL`というメッセージが表示されてしまうので、external-scripts.jsonからheroku packageを削除。

##Dockerの用意

- Dockerfileを書く
- hubotの実行にnode.jsが必要なので、Docker registryからnode:latestを持ってくる。-> https://registry.hub.docker.com/u/library/node/
- あとは、ローカル側に用意したhubotの構成をDockerインスタンス上に移設して起動する設定をDockerfileに書く。

##Dockerを起動してみる
####まずはbuild

`% docker build -t hubot .`

そうすると、イメージが作成される。

```zsh
% docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
hubot               latest              663c24a4b22a        54 seconds ago      717.4 MB
```

作成されたイメージ上で、hubotを起動してみる

```zsh
% docker run -it hubot bin/hubot
hubot> hubot ping
PONG
```

##Slackの用意
#### Integration Settings
- Hubotを有効に。
  - API Tokenを得られる
  - Iconのカスタマイズ、Nameのカスタマイズもできる
 
##Dockerの起動

```zsh
% docker run -e HUBOT_SLACK_TOKEN=${HUBOT_SLACK_TOKEN} -e HUBOT_SLACK_TEAM=${HUBOT_SLACK_TEAM} -e PORT=${PORT} -d -p 9999:9999 -v "$(pwd)":/hubot hubot
```

これでチームにhubotが参加してくる。

##CircleCIの用意

```circle.yml
machine:
  services:
    - docker
dependencies:
  cache_directories:
    - "~/docker"
  pre:
    - if [[ -e ~/docker/hubot.tar ]]; then docker load --input ~/docker/hubot.tar; fi
    - docker build -t hubot .
    - mkdir -p ~/docker
    - docker save hubot > ~/docker/hubot.tar
test:
  override:
    - docker run -p 9999:9999 -v "$(pwd)":/hubot hubot
```

- circleCI上でDockerコンテナを起動するときは、services: dockerが必要
- dependenciesのところは、dockerのイメージを毎回registryからダウンロードしなくてよいようにキャッシュしている

##Hubotへスクリプトの追加

```stamp.coffee
# Description:
#   A way to interact with the Stamp URI.
#
# Commands:
#   hubot stamp me <keyword> - The Original. Queries Stamp Images for <keyword> and returns a stamp image.
#   hubot stamp list         - Show a list of keywords.

stamps = JSON.parse(process.env.HUBOT_STAMPS)

module.exports = (robot) ->
  robot.respond /stamp me (.*)/i, (msg) ->
    keyword = msg.match[1]
    msg.send stamps[keyword] ? "No stamp for #{keyword}"

  robot.respond /stamp list/i, (msg) ->
    keys = for key, value of stamps
             key
    msg.send keys.join('\n')
```

`scripts`以下に配置するとhubotの起動時に読み込んでくれる。
決められたフォーマットでコメントを書いておくと、`hubot help`したときにコマンドの説明を表示してくれる。このscriptでは環境変数`HUBOT_STAMPS`にJSONを登録しておくと、keyに紐付いたimageのURLを返してくれる。

##AWSへAnsibleを使ってデプロイする

```hubot.yml
- hosts: aws
  user: ec2-user
  vars_files:
    - vars/env.yml
    - vars/private.yml
  tasks:
    - git: repo=https://github.com/osamunmun/hubot.git dest=/home/ec2-user/hubot accept_hostkey=yes
    - file: path=/home/ec2-user/cache state=directory
    - shell: cd /home/ec2-user/hubot; sudo ./docker-build.sh
    - shell: sudo docker stop $(sudo docker ps -a -q)
    - shell: cd /home/ec2-user/hubot; sudo docker run -e HUBOT_STAMPS={{ HUBOT_STAMPS }} -e HUBOT_SLACK_TOKEN={{ HUBOT_SLACK_TOKEN }} -e HUBOT_SLACK_TEAM={{ HUBOT_SLACK_TEAM }} -e PORT={{ PORT }} -p 9999:9999 -d -v $(pwd):/hubot hubot/slack
```

##CircleCIからデプロイする

- circleCIの環境変数に以下の値を登録しておく
  - ANSIBLE_VAULT
  - AWS_ACCESS_KEY_ID
  - AWS_DEFAULT_REGION
  - AWS_SECRET_ACCESS_KEY
  - AWS_SECURITY_GROUP_ID
  - HUBOT_SLACK_TEAM
  - HUBOT_SLACK_TOKEN
- circleCIのSSH Permissionsにデプロイ先のEC2インスタンスへアクセスできる秘密鍵を登録しておく

```circle.yml
machine:
  services:
    - docker
dependencies:
  cache_directories:
    - "~/cache"
  pre:
    - ./docker-build.sh
    - npm install
    - sudo pip install awscli
    - sudo pip install ansible
    - echo "${ANSIBLE_VAULT}" > ~/vault.txt
test:
  override:
    - ./node_modules/.bin/gulp mocha
    - docker run -e HUBOT_SLACK_TOKEN=${HUBOT_SLACK_TOKEN} -e HUBOT_SLACK_TEAM=${HUBOT_SLACK_TEAM} -e PORT=${PORT} -d -p 9999:9999 -v "$(pwd)":/hubot hubot/slack
deployment:
  production:
    branch: master
    commands:
      - sh ./deploy.sh
```

```deploy.sh
#!/bin/sh
set -ex

IP=`curl -s ifconfig.me`

trap "aws ec2 revoke-security-group-ingress --group-id ${AWS_SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-id ${AWS_SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32
cd ansible; ansible-playbook -i hosts hubot.yml --vault-password-file ~/vault.txt
```

```ansible/hubot.yml
- hosts: aws
  user: ec2-user
  vars_files:
    - vars/env.yml
    - vars/private.yml
  tasks:
    - git: repo=https://github.com/osamunmun/hubot.git dest=/home/ec2-user/hubot accept_hostkey=yes
    - file: path=/home/ec2-user/cache state=directory
    - shell: cd /home/ec2-user/hubot; sudo ./docker-build.sh
    - shell: sudo docker stop $(sudo docker ps -a -q)
    - shell: cd /home/ec2-user/hubot; sudo docker run -e HUBOT_STAMPS={{ HUBOT_STAMPS }} -e HUBOT_SLACK_TOKEN={{ HUBOT_SLACK_TOKEN }} -e HUBOT_SLACK_TEAM={{ HUBOT_SLACK_TEAM }} -d -v $(pwd):/hubot hubot/slack
```