Backlog の Git Webhook を試した。

はじめに

いやね、もう何年も前から手をつけないといけないなとは思っていたのですよ。でもボク、CIの類とか知らんし使っとらんし、git pull するだけなら認証とか云々ゴニョゴニョするの面倒だし気持ち悪いしなーとか何とかで放っておきました。けども、いい加減つかみだけでも、仕組みだけでも触っておくかと重い腰を上げた次第でございます。

Backlog の Git Webhook

まずは仕様的なものを確認しておきましょう。

どうやら指定した URL へ向かって JSON 形式のデータを POST してくれるようですね。そしてまぁ、その JSON データは payload という名前で以下のような感じで送られてくるっぽいです。

{
  "before": push前のコミット,
  "after": push後のコミット,
  "ref": 参照
  "repository": {
    "url": リポジトリのURL,
    "name": リポジトリ名,
    "description": リポジトリの説明,
  },
  "revisions": [{
    "id": コミットのID,
    "url": コミットのURL,
    "author": {
      "email": コミットした人のメールアドレス,
      "name": コミットした人の名前
    },
    "message": コミットメッセージ,
    "timestamp": タイムスタンプ,
    "added": [ 追加されたファイル ],
    "removed": [ 削除されたファイル ],
    "modified": [ 修正されたファイル ],
  }]
}

なるほど、これを指定した URL 上の何かしらで受け取っていい感じに処理すれば良さそうです。何かしらって普通に CGI 的な何かですよね、PHP にしておけば面倒もなさそうで他の人にとっても使い勝手が良さそうと思ったり何だりもしましたが、普段書いているし、実は CGI 的なの書いたことがなかったので Python にすることにしました。

Python の cgi と http.server で

どうやら Python さんでは cgi モジュールを使うとササっとことを運べそうです。

また HTTP サーバーとしてもサクっと起動できて動作確認に使えそうですよ、楽々ですねぇ。

python -m http.server --cgi

こんな感じで起動しますと CGI も受け入れてくれますが、直下に cgi-bin というフォルダーを作ってそこにスクリプトを置かないといけないらしいですよ、気をつけましょう。ちなみにデフォルトで待ち受ける Port は 8000 番です。

cgi.FieldStorage

じゃまぁ、CGI スクリプトを書いていきましょう。HTML で Form 入りのページを作ってそこから CGI スクリプトへ向かって POST してもいいですけども、curl なり何なりを使えばコマンドラインから POST することができて便利です。上記 Backlog さんのページにサンプルデータもありましたので、それをお借りして投げつけます。

curl -X POST -d 'payload="{
  "before": "5aef35982fb2d34e9d9d4502f6ede1072793222d",
  "after": "c0b8abaa6df37ea682454c25f2d602dffb5de6ed",
  "ref": "refs/heads/master",
  "repository": {
    "url": "https://demo.backlog.jp/git/DORA/himitsu",
    "name": "himitsu",
    "description": "ひみつ道具を格納しているリポジトリ"
  },
  "revisions": [
    {
      "id": "8e82fe274af30adbb378785628db509da1c969d1",
      "url": "https://demo.backlog.jp/git/DORA/himitsu/commit/8e82fe274af30adbb378785628db509da1c969d1",
      "author": {
        "email": "nobi@example.com",
        "name": "nobi"
      },
      "message": "どこでも行けるドアを作成しました",
      "timestamp": "2013-04-01T14:57:17+09:00",
      "added": ["html/anywhere.html", "css/anywhere.css"]
    },
    {
      "id": "c0b8abaa6df37ea682454c25f2d602dffb5de6ed",
      "url": "https://demo.backlog.jp/git/DORA/himitsu/commit/c0b8abaa6df37ea682454c25f2d602dffb5de6ed",
      "author": {
        "email": "gouda@example.com",
        "name": "gouda"
      },
      "message": "DORA-1 竹でできているヘリコプターが壊れました #fixed",
      "timestamp": "2013-04-01T18:22:10+09:00",
      "removed": ["html/take-copter.html"]
    }
  ]
}"' localhost:8000/cgi-bin/hook.cgi
#!/usr/bin/env python3
import cgi
import json

d = cgi.FieldStorage()
p = json.loads(d['payload'].value)

...

はい、あとは取り出した JSON データ(p)の中を参照しながら望むようにごにょごにょすればいいですね、簡単。ここから先が大変そうけど。

application/json ?

実はこうササっとことは運んでいなくて、最初に仕様を確認した時に「なるほど、JSON か、じゃあ application/json で送ってくるんだな」とか思ってしまって間違ったことをしていたのでした。悔しいのでその時のやーつも書いときますね。

curl -X POST -H "Content-Type: application/json" -d '{ "payload": { ... } }' localhost:8000/cgi-bin/hook.cgi
#!/usr/bin/env python3
import os
import json
import sys

l = os.environ.get('CONTENT_LENGTH', '0')
b = sys.stdin.buffer.read(int(l))
p = json.loads(b.decode())['payload']

...

何でも Python で application/json で受け取るときは標準入力を用いるのだそうですよ。

助かりました、ありがとうございます!

おわりに

次はここから何をしようとするかと思うのですけど、私はこのスクリプトを汎用的に使いたいと思って、Project 毎に用意している更新処理的なスクリプト(単に git pull するだけではなくて build っぽいのを行うものだったりも)を subprocess で叩くみたいなことを想定して試しましたよ。この辺のはまた別の機会に書いたりしようかな。

誰かの何かの参考になれば幸いです!

かいているひと

しもだ たくろう a.k.a. たくぅ

1996年くらいからこんなことをやっているのでなかなかのおじさんです。一応今もWeb系の仕事はやっているぽい。実がないので口の方で煙に巻きます。「ぺちぱな。」ん。くず界のくず。 SW-2066-7468-5602✨