個人用の 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
というヘッダに署名をつけてくれるようになる。
署名は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 されたあとに各スクリプトを読みに行っているのでうまくいきそうなんだけれど…。ファイルの読み込みが終わった段階でもイベントが欲しい。
何かいい方法があれば知りたい。