Github、Bitbucketとのデプロイ連携するときのBottle用のテンプレート

自分用の[Bottle](http://bottlepy.org/)用テンプレート。

Bitbucket、GithubのHook用APIは、pullした後、[Upstart](https://ja.wikipedia.org/wiki/Upstart)に登録してるAPIサーバー（[gunicorn](http://gunicorn.org/)）を再起動してる。

Gitレポジトリの操作用に、[GitPython](https://github.com/gitpython-developers/GitPython)を入れてる。

時間周りの処理で、[arrow](http://crsmithdev.com/arrow/)も入れてる。

```py:server.py
# -*- coding: utf-8 -*-

import commands
import os.path
import traceback
from decorator import decorator
import logging
import subprocess
import getpass

from bottle import get, post, put, delete, run, default_app, request, response, HTTPError, redirect, local, abort 
from bottle import static_file, route

import arrow

from git import Repo


# Decorator

@decorator
def check_db_connection(func, *args, **kwargs):
    """
    Peewee用。dbは、Peeweeのデータベースのインスタンス。
    """

    try:
        db.get_conn().ping(True)
    except:
        logging.error(traceback.format_exc())
    
    return func(*args, **kwargs)

@decorator
def check_login(func, *args, **kwargs):
    """
    認証処理を書いて、ログインしたユーザーのインスタンスをlocal.meとかに入れとく。
    """

    response_json = func(*args, **kwargs)
    return response_json

@decorator
def error_handling(func, *args, **kwargs):
    """
    エラーをJSON形式で返す。デバッグ用。本公開の時は、error_classとかerror_traceは消す。
    """

    try:
        return func(*args, **kwargs)
    except Exception as exception:
        if issubclass(exception.__class__, HTTPError):
            raise exception
        else:
            logging.error(traceback.format_exc())

            return {
                "error_class": type(exception).__name__,
                "error_trace": traceback.format_exc(),
            }

@decorator
def measurement(func, *args, **kwargs):
    """
    APIの処理時間を計測する。
    """

    start_time = arrow.now()

    result = func(*args, **kwargs)

    print "Transaction time: {0} secs".format(arrow.now().float_timestamp - start_time.float_timestamp)

    return result


# API


# Static files

@route('/')
@route('/<filepath:path>')
def server_static(filepath = None):
    if filepath is None:
        filepath = 'index.html'

    root_path = u"Webのルートパス"

    response = static_file(filepath, root = root_path)
    return response


# Pull from Bitbucket

@post('/__pull_from_bitbucket')
@error_handling
def pull_from_bitbucket():
    """
    Bitbucket連携用。
    IPで、このAPIへのアクセスをBitbucketからだけに絞っとく。
    """

    repository_path = "{0}/../../..".format(os.path.dirname(__file__))
    repository = Repo(repository_path)

    payload = request.json

    logging.info("payload: {0}".format(payload))
    branch = payload['push']['changes'][0]['new']['name']

    logging.info("Pull from Bitbucket: {0}: {1}".format(
        branch, repository.active_branch.path))

    if repository.active_branch.path.endswith(branch):
        repository.remotes[0].pull()

        reboot_gunicorn_command = ["/usr/bin/sudo", "/sbin/restart", "api-server"]
        output = subprocess.check_output(reboot_gunicorn_command)
        logging.info("Reboot API server: {0}: {1}".format(
            reboot_gunicorn_command, output))

    return {
    }


# Pull from GitHub

GITHUB_HOOK_SECRET = os.environ.get('GITHUB_HOOK_SECRET')

@post('/__pull_from_github')
@error_handling
def pull_from_github():
    """
    GitHub連携用。
    GitHub Hookのシークレットキーを、環境変数に入れとく。
    """

    sent_signature = request.headers.get('X-Hub-Signature')[5:]
    raw_payload = request.body.read()
    generated_signature = hmac.new(GITHUB_HOOK_SECRET, raw_payload, hashlib.sha1).hexdigest()

    if sent_signature == generated_signature:

        repository_path = "{0}/../../..".format(os.path.dirname(__file__))
        repository = Repo(repository_path)
    
        payload = request.json
    
        if payload['ref'] == repository.active_branch.path:
            logging.info("Pull from GitHub: {0}: {1}".format(payload['ref'], payload['head_commit']['id']))
            repository.remotes[0].pull()
    
            reboot_gunicorn_command = ["/bin/sudo", "/usr/bin/systemctl", "restart", "api_server"]
    
            logging.info("Reboot API server: {0}".format(reboot_gunicorn_command))
            return_code = subprocess.call(reboot_gunicorn_command)

    return {
    }




application = default_app()
if __name__ == '__main__':
    run(host = 'localhost', port = 8000, debug = True)
```