Buildbotことはじめ

CI環境が欲しければ[Circle CI](https://circleci.com/)なり[Travis CI](https://travis-ci.org/)なりを使うのが一般的だが、クローズドなリポジトリを運用している場合は自前で立てる必要がある。

よくあるのは[Jenkins](https://jenkins.io/)なのだろうが、今回はリポジトリがmercurialかつFlaskのプロジェクトが対象だったので、なんとなくPythonで揃えて[Buildbot](http://buildbot.net/)を選択してみた。

* ことはじめ
* [設定ファイル](https://qiita.com/nothink/items/6a8cac78e45eccd2d5ad)

# Buildbotの仕様

[Buildbot Basics](http://buildbot.net/index.html#basics)を読んでもらえばわかるのだが、BuildbotはMasterとWorkerのふたつで構成される。

![overview.png](https://qiita-image-store.s3.amazonaws.com/0/79463/c14fd229-883b-d8cf-7a3a-332ae97914ad.png)

MasterはTwistedで書かれているようで、リポジトリへのポーリングやWorkerへの依頼、Webインターフェイスなどをこなす。WorkerはMasterから依頼されて実際のビルド処理やテストなどを行う。

# チュートリアル

試しにMasterとWorkerを作って連携するところまで試してみよう。
ここでは、作成直後に記述されているGithub上の [buildbot/hello-world](https://github.com/buildbot/hello-world) で構築する。

やり方は[First Run](http://docs.buildbot.net/current/tutorial/firstrun.html)にあるのでそちらを参照にしつつ、今回は手頃なEC2インスタンス(Ubuntu)上に構築してみた。

Docker環境がある人は手っ取り早く[First Buildbot run with Docker](http://docs.buildbot.net/current/tutorial/docker.html)をどうぞ。

## Master作成

適当なvirtualenvを立ててpipで導入。

```shell-session
$ virtualenv buildbot -p python3
$ source buildbot/bin/activate
$ pip install --upgrade pip
$ pip install 'buildbot[bundle]'
```

必要なのは `python3`, `virtualenv`, `python3-dev` と、あとデフォルトだとSQLiteなので `sqlite` くらいだろうか。

導入次第、適当なディレクトリに移ってMasterを作ってみる。

```shell-session
$ buildbot create-master master
mkdir /home/ubuntu/sites/master
creating /***/***/master/master.cfg.sample
creating database (sqlite:///state.sqlite)
buildmaster configured in /***/***/master
$ cp master/master.cfg.sample master/master.cfg
```

お好みで設定ファイル `master/master.cfg` を修正。
masterディレクトリと同じ階層に移動し、Master開始。

```shell-session
$ buildbot start master
Following twistd.log until startup finished..
The buildmaster appears to have (re)started correctly.
```

これで、 `localhost:8010` にMasterが立つ。見てみるとこんな感じ。

<img width="772" alt="名称未設定.png" src="https://qiita-image-store.s3.amazonaws.com/0/79463/566ad3fa-6c93-102b-aa37-7d22b589393f.png">

もしnginxなどで外部に80番で出したい場合、`/ws` でWebsocket通信しているのでそこだけ注意。また、 `/sse/` は一応バッファリングしない方が良い。

```nginx:subdomain.domain.tld.conf
server {
    listen 80;
    listen [::]:80;

    server_name subdomain.domain.tld;

    proxy_set_header HOST $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto  $scheme;
    proxy_set_header X-Forwarded-Server  $host;
    proxy_set_header X-Forwarded-Host  $host;

    location / {
        proxy_pass http://localhost:8010;
    }
    location /sse/ {
        proxy_buffering off;
        proxy_pass http://localhost:8010/sse/;
    }
    location /ws {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://localhost:8010/ws;
        proxy_read_timeout 6000s;
    }
}
```

## Worker作成

次にWorkerの構築。
Masterの環境にも `buildbot-worker` は導入されているので同じvirtualenvを用いても良いのだが、実運用だとWorkerにしか入れないパッケージなどもありそうなのでvirtualenvを新規作成。

```shell-session
$ virtualenv worker -p python3
$ source worker/bin/activate
$ pip install --upgrade pip
$ pip install buildbot-worker
```

Workerはテストを行う側なので、テストプロジェクト内で使われている `runtests` を導入。

```shell-session
$ pip install setuptools-trial
```

Workerを構築する前に、Master側の `master.cfg`の以下を確認（必要ならば修正）しておく。
これがMasterとWorkerが会話するためのユーザとパスワードになる。

```py3:master.cfg
####### WORKERS

# The 'workers' list defines the set of recognized workers. Each element is
# a Worker object, specifying a unique worker name and password.  The same
# worker name and password must be configured on the worker.
c['workers'] = [worker.Worker("example-worker", "pass")]
```

`example-worker` がユーザ、`pass` がパスワードとなる。

同じく接続先の確認。

```py3:master.cfg
# 'protocols' contains information about protocols which master will use for
# communicating with workers. You must define at least 'port' option that workers
# could connect to your master with this protocol.
# 'port' must match the value configured into the workers (with their
# --master option)
c['protocols'] = {'pb': {'port': 9989}}
```

ポート `9989` の定義のみなのでこのままローカル（無指定だと規定値の`9989`）で繋ぐ。

```shell-session
$ buildbot-worker create-worker worker localhost example-worker pass
mkdir /***/***/worker
mkdir /***/***/worker/info
Creating info/admin, you need to edit it appropriately.
Creating info/host, you need to edit it appropriately.
Not creating info/access_uri - add it if you wish
Please edit the files in /***/***/worker/info appropriately.
worker configured in /***/***/worker
```

　では、Masterと同じくWorkerも走らせる。

```shell-session
$ buildbot-worker start worker
Following twistd.log until startup finished..
The buildbot-worker appears to have (re)started correctly.
```

`lsof` あたりで確認すると次のようになってるはず。

```shell-session
$ lsof -i
COMMAND    PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python3   xxxx   user   00u  IPv4  yyyyy      0t0  TCP *:8010 (LISTEN)
python3   xxxx   user   00u  IPv4  yyyyy      0t0  TCP *:9989 (LISTEN)
python3   xxxx   user   00u  IPv4  yyyyy      0t0  TCP localhost:9989->localhost:37564 (ESTABLISHED)
buildbot- xxxx   user   00u  IPv4  yyyyy      0t0  TCP localhost:37564->localhost:9989 (ESTABLISHED)
```

この段階で既に `master/master.cfg` で定義されているリポジトリに対して `gitpoller` がポーリングを仕掛け始めているはず。

## ビルド実行

ポーリング先に変更があればビルドが走るわけだが、今回はGitHub上の公開リポジトリなので変更を発生させるのも面倒なので、強制的にビルドを走らせてみる。

Builds / Builders の中を見てみると、

<img width="835" alt="test1.png" src="https://qiita-image-store.s3.amazonaws.com/0/79463/0db4cee1-b95c-22d8-a030-ce3d3f11b3e9.png">

既に `runtests` というビルダーが存在するので、これを強制的にビルドする。

<img width="832" alt="test2.png" src="https://qiita-image-store.s3.amazonaws.com/0/79463/1531d345-f517-7990-3a3d-16687a66eebc.png">

適当に入力（空で良い）して実行すると、ビルド経過が次々と出てきた後でビルド結果が表示される。
これが、実際はポーリングで変更を感知した都度実施されると思えばいい。

<img width="1034" alt="test3.png" src="https://qiita-image-store.s3.amazonaws.com/0/79463/80ebdbb8-e37b-f1ee-05d0-5a29922bd7b5.png">

## 認証の追加

Master側に組み込みの認証を設定してみよう。
設定は `c['www']['auth']` と `c['www']['authz']` で行う。

```py3:master.cfg
# minimalistic config to activate new web UI
c['www'] = dict(port=8010,
                plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
```

の下あたりから追加。
追加ユーザーは今回はテストのため埋め込みで [Ponapalt/Nullpo](https://twitter.com/ponapalt) とした。

```py3:master.cfg
c['www'] = dict(port=8010,
                plugins=dict(waterfall_view={}, console_view={}, grid_view={}))

c['www']['authz'] = util.Authz(
        allowRules = [
            util.AnyEndpointMatcher(role="admins")
        ],
        roleMatchers = [
            util.RolesFromUsername(roles=['admins'], usernames=['Ponapalt'])
        ]
)
c['www']['auth'] = util.UserPasswordAuth([('Ponapalt','Nullpo')])
```

これで `reconfig` すると右上にログイン項目が増えてアクセス制限がかかる。

<img width="850" alt="test4.png" src="https://qiita-image-store.s3.amazonaws.com/0/79463/00510b06-71fc-683b-a610-3b9519025d66.png">


チュートリアルとしてはこんな感じだろうか。

次　→　[Buildbotの設定ファイル](https://qiita.com/nothink/items/6a8cac78e45eccd2d5ad)