レポジトリ・trac 生成 CGI を作る
先日の続きです。SVNParentPath と TracEnvParentDir を活用してないととくにおいしくない話です。
先日のように私用環境では Subversion のレポジトリと Trac のディレクトリの owner は apache にしてあります。このままだと新規レポジトリを作成したりするのにいちいち su apache になったりしなきゃいけないのが面倒なので,じゃあいっそウェブアプリ化してみよう,ということです。
アプローチ
ぶっちゃけ Python のコードを読み書きしたのは今回が初めてなので見よう見まねです(全然 try 等で例外をキャプチャしてねー)。
Python には CGI, FastCGI, mod_python 等の差異を吸収してくれる WSGI というインタフェースがあるのですが(WSGI については後述のリンク先にある id:tokuhirom さんの記事が詳しい),Trac ではさらにその上に CGIGateway, ModPythonGateway 等のラッパが用意されていたのでそれらを再利用しました。
Subversion のレポジトリツリーは,かつて記述したように svn.repos.create(ほげほげ, ...) で作成します。
trac-admin コマンドまわりは機能単位でコンポーネント化されているのかと思ったら,trac.scripts.admin.py にだらだらと書かれていたんで,標準出力・標準エラー出力をトラップする必要がありました。
参考リンク
- 入門
- ディストリビューションの作り方
- 標準出力のトラッピングについて
- WSGI について
インストール
ソースツリーは
+-+- setup.py +-+- trac_webadmin/ +--- main.py +--- modpython_fe.py
こんな感じで,
% sudo python setup.py install
とかすると site-packages 以下にインストールされます。
先日の trac.conf に以下のように付け加えます。
<Location /trac/admin/create> # SetHandler mod_python PythonHandler trac_webadmin.modpython_fe # PythonOption TracEnvParentDir /var/www/trac PythonOption TracEnvReposParentDir /var/www/repos </Location>
mod_python と TracEnvParentDir は先日の例だと上位 location で設定されているので,ここの例ではそれらを継承したとみなしてコメントアウトしてます。
ソース
trac_webadmin/main.py は以下の通り
# -*- coding: utf-8 -*- import os import sys import cgi import svn.core import svn.repos from trac.web.api import * from trac.scripts.admin import TracAdmin from trac.config import default_dir def escape_quot(s): return s.replace('"', '\\"') class StreamSweeper: def __init__(self): self.stock = '' def write(self, s): self.stock += s class App: def __init__(self, req): self.req = req def run(self): if self.req.method == 'POST': self.action_create() else: self.show_form() def action_create(self): req = self.req if req.args['repos'] == '' or req.args['project'] == '': return self.show_error('Invalid Parameters') path_repos = req.environ.get('trac_webadmin.env_repos_parent_dir') \ + '/' + req.args['repos'] path_trac = req.environ.get('trac_webadmin.env_parent_dir') \ + '/' + req.args['repos'] try: svn.repos.create(path_repos, '', '', None, None) except Exception, e: return self.show_error( 'svn.repos.create failed: %s' % e ) last_stdout = sys.stdout sys.stdout = StreamSweeper() last_stderr = sys.stderr sys.stderr = StreamSweeper() log_stdout = '' r = 0 succeeded = False try: admin = TracAdmin() admin.env_set(path_trac) r = admin.do_initenv( '"%s" %s %s "%s" "%s"' % ( escape_quot(req.args['project']), 'sqlite:db/trac.db', 'svn', escape_quot(path_repos), escape_quot(default_dir('templates')) ) ) log_stdout = sys.stdout.stock succeeded = True finally: sys.stdout = last_stdout sys.stderr = last_stderr if succeeded: if r > 0: self.show_error(log_stdout) else: self.show_created(log_stdout) else: self.show_error('TracAdmin.do_initenv failed') def _send_headers(self, status = 200): req = self.req req.send_response(status) req.send_header('Content-Type', 'text/html; charset=UTF-8') req.end_headers() def show_form(self): self._send_headers() self.req.write(""" <html> <head> <title>プロジェクトの作成</title> <style type="text/css"> th { text-align: left; } </style> </head> <body> <h1>プロジェクトの作成</h1> <form method="post"> <table> <tbody> <tr> <th>レポジトリ名</th> <td><input type="text" name="repos" /></td> </tr> <tr> <th>プロジェクト名</th> <td><input type="text" name="project" value="My Project" /></td> </tr> <tr> <th></th> <td><input type="submit" /></td> </tr> </tbody> </table> </form> </body> </html> """) def show_created(self, log_stdout = ''): self._send_headers() self.req.write(""" <html> <head> <title>プロジェクトの作成 - 成功</title> </head> <body> <h1>プロジェクトの作成</h1> <p>成功しました</p> <pre>%s</pre> </body> </html> """ % ( cgi.escape(log_stdout).encode('utf-8') ) ) def show_error(self, msg = ''): self._send_headers(500) self.req.write(""" <html> <head> <title>プロジェクトの作成 - エラー</title> </head> <body> <h1>プロジェクトの作成</h1> <p>エラーが発生しました</p> <pre>%s</pre> </body> </html> """ % ( cgi.escape(msg).encode('utf-8') ) ) def dispatch_request(environ, start_response): if 'mod_python.options' in environ: options = environ['mod_python.options'] environ.setdefault('trac_webadmin.env_parent_dir', options.get('TracEnvParentDir')) environ.setdefault('trac_webadmin.env_repos_parent_dir', options.get('TracEnvReposParentDir')) else: environ.setdefault('trac_webadmin.env_parent_dir', os.getenv('TRAC_ENV_PARENT_DIR')) environ.setdefault('trac_webadmin.env_repos_parent_dir', os.getenv('TRAC_ENV_REPOS_PARENT_DIR')) req = Request(environ, start_response) app = App(req) app.run() return req._response or []
trac_webadmin/modpython_fe.py は以下の通り。
# -*- coding: utf-8 -*- from mod_python import apache from trac.web.modpython_frontend import ModPythonGateway from trac_webadmin.main import dispatch_request def handler(req): options = req.get_options() gateway = ModPythonGateway(req, options) gateway.run(dispatch_request) return apache.OK
setup.py は以下の通り。
#!/usr/bin/python # -*- coding: utf-8 -*- from distutils.core import setup setup(name='trac-admin web interface', version='0.1', packages=['trac_webadmin'])