ちなみに

火曜日の空は僕を押しつぶした。

Hubot のデプロイ開始を自分で報告させるようにした話

photo by Matt Biddulph

個人用の Slack に Hubot を入れた。会社のやつはサーバーに置いてあったので、GitHub に push して、チャット上から update を指示するという形だったけれど、個人のは面倒なので Heroku に置いて GitHub 連携で push したら自動的にデプロイするようにした。

デプロイしていることを視覚的に分かるようにしたかったので、Webhook で push したことを通知して、デプロイ中であることを表示するようにしてみた。いくつかハマったのでメモ。

HTTP Listener

Hubot は起動すると express を使って Web サーバーを立ち上げる。これを使って HTTP リクエストを受け付ける HTTP Listener を実現している。これを使って /hubot/gh-commits に Webhook を受け取ることにする。

最初は何も考えずに以下のように書いていた。

robot.router.post "/hubot/gh-commits", (req, res) ->
    data = JSON.parse(req.body.payload)
    robot.messageRoom "general", "[deploy] #{data.user.login}: deploying #{dat.ref} - #{data.compare}"
    res.send "ok"

これは実は間違いで、payload なんてパラメータはなくて body にそのまま JSON が入っている。

ではこうかと思ったらこれも違った。JSON のパースエラーになる。

data = JSON.parse(req.body)

実は Hubot は express を立ち上げるときに bodyParser という Middleware を組み込んでいる。これは クエリストリング か body かなどによらず、JSON っぽかったら JSON みたいに空気読んでパースして Object にしておいてくれる。

つまり、req.body はすでに Object になっているのだった。

data = req.body

secret

GitHub の Webhook には secret を指定出来る。これを指定しておくと、リクエストの X-Hub-Signature というヘッダに署名をつけてくれるようになる。

f:id:Sixeight:20150224113433p:plain

署名はDigest のアルゴリズム (この場合は sha1)と、本体の JSON を指定した secret を使って HMAC-SHA1 の hexdigest にしたものを = でくっつけたものになっている。(コード)

別に偽造されたところで被害はないんだけれど、これも一応チェックしておきます。

signature = req.header("X-Hub-Signature")

hmac = crypto.createHmac("sha1", "secret")
hmac.update(JSON.stringify(req.body)) # Object になってしまっているので
hashed_data = hmac.digest("hex")
generated_signature = "sha1=#{hashed_data}"

if signature is generated_signature
    # 処理

復帰報告

デプロイが始まるのはいいのだけれど、終わったことも報告して欲しい。 最初は単純にスクリプトが読み込まれた瞬間に発言すれば良いのではと思ったのだけれど、どうも上手くいかなくて、setTimeout するハメになった。 connected イベントが emit されたあとに各スクリプトを読みに行っているのでうまくいきそうなんだけれど…。ファイルの読み込みが終わった段階でもイベントが欲しい。

何かいい方法があれば知りたい。

最終的なコード