Undefined Title

Undefined Title

githubのwebhook用サーバをつくった

githubのWebhook、皆さん使ってますか。Webhookを受け取るサーバって何使ってますか、sinatraですか?このエントリは社内でserfのデモ用に書いた、github webhook用のHTTPサーバhoko in golangについてのエントリです。

TL;DR

  • hokoというgithubのwebhookを受け取るためのHTTPサーバをつくった。
    • https://github.com/tmtk75/hoko
    • x-github-eventヘッダを解釈するよ
    • x-hub-signatureに対応してるからsecurityもケアしてるよ
    • みんな大好きgolangで書いたよ
  • serfと連携、というかserfの関数をそのまま呼び出すよ
  • hokoが作るクラスタにjoinしておけば、webhookイベントを他のホストで受けることが比較的簡単にできるよ

背景

  • githubへのpushされたらpuppetmasterを更新したい、みたいなユースケースでwebhook使ってる、もともとあったサーバは適当に書いたRubyのコードでやっつけ感が半端なかった。
  • メンテナビリティなしっぽい。特に、実際にやらせたいことを書いたシェルの呼び出し部分とか使い回しできなさそう。
  • これを他のいろんなホストに複数deployするのなんかちょっとイヤ。ポートもそれぞれ開けなきゃいけないし、webhook受け取るホストって一箇所にできないかな?

みんなどうしてるんだろう、という疑問とともにさらなる要求も。

  • 公開するcallbackのエンドポイントはひとつで、そのホストでいろいろさせられたらいいなあ。
  • でもあんまり複雑な仕組みはいれたくないし、自分で書くのも嫌だな。

それserfでできるよ

もとあったRubyのコードをgolangで書き直したいなあと思って、最初はwebhookイベントに対応した名前のshellスクリプト呼び出すような簡易サーバ書いてたんだけど、ちょっと条件つけようとするとタグとかそういう仕組が欲しくなってきて、複雑になりそうなところで思いとどまること数時間。

そんなところに、ふとHashiCorpの神が降りてきた。
serfです、serfを使うのです、と。

  • serfのイベントハンドラはコマンドを実行できて応答を返せる
  • タグを使ってイベントを分類できる
  • 必要なら他のホストへも伝搬させられる
    [github]
       |
       |  webhook
       |
       `---- POST ----> [hoko]  invoke serf query
                          A
                          |  joining cluster 
                          |
                        [serf agent]
                          |
                          |  exec by query
                          |
                           `----> [event-handler]

つまりイベントハンドラの登録、タグの指定方法のI/Fにserfを使って一般化しようという意図です。そこら辺を自分で作りこむとオレオレI/Fになってしまうので、できるだけありものを使いたかった、と。

webhookを受け取る部分は、認証とbodyのparse、serf queryの発行だけをやる、つまりserfのgithub webhook用HTTP interfaceってこと。

設定や実行方法

設定や実行方法はREADMEに詳しく書いた。ローカルでも動作確認はできるのでイベントハンドラのテストも簡単にできる。また実際イベントを受けてからの動作はserfのレイヤなのでhoko関係なく考えられる。

一応ここでも簡単に触れると、バイナリはここからダウンロード、そしてこんな設定ファイルをバイナリと同じディレクトリにserf.confとして置く。これはserfの設定ファイルそのまま。

{
  "tags": {
    "webhook": ".*"
  },
  "event_handlers": [
    "query:hoko=./handler/echo.sh"
  ]
}

あとはイベントハンドラにあたるコマンド./handler/echo.shを用意する。

$ ./hoko run -d &
==> Starting Serf agent...
==> Starting Serf agent RPC...
...
    2014/07/27 10:18:04 [INFO] agent: Received event: member-join

$ curl localhost:3000/serf/query/hoko -d '{}'
{
  "Acks": [
    "hostname"
  ],
  "Responses":
    "hostname": "\n------- ./handler/echo.sh\nSERF_EVENT: query\nSERF_QUERY_NAME: hoko\nSERF_SELF_NAME: hostname\nSERF_TAG_WEBHOOK: .*\npayload: \"event\":\"\",\"ref\":\"\",\"after\":\"\",\"before\":\"\"}\n\n\n"
  }
}

hokoを実行してcurlでリクエストを投げると、上記のようなレスポンスが返ってくる。Responsesのhostnameの値、これは./handler/echo.shの出力だ。hostnameは実際は実行したホスト名が入る。

ちょっとだけ気をつけないといけないのは、serfのpayloadは1024byteまでだから、webhookのPOSTのbodyはそのまますべてをイベントハンドラへは渡せないこと。今は適当にピックアップしたフィールドをpayloadとしています。ref,after,before、あとはx-github-eventヘッダのをeventとして。

今のところコードはimport含めて200行もないので何やってるかはすぐ見通せるはず。

締め

hokoはserfのquery起動のためのHTTPのインタフェースを提供するもので、今はx-github-event解釈とかbodyのJSON schemaはgithub用だよという感じ。

今後はクエリパラメータでタグを指定するとか、serf eventコマンドも発火できるようにするとかの改良を入れたい。

あと、一番めんどくさかったのは正直x-hub-signatureverifyでした。ぜんぜん一致しなくて泣きそうだった...。