From 8a0654abe2db919b04683d40100b469e8c3adc4b Mon Sep 17 00:00:00 2001 From: Tommi Virtanen Date: Mon, 3 Sep 2007 17:04:41 -0700 Subject: [PATCH] Refactor command line utilities to share setup. Hide internal gitosis-ssh and gitosis-gitweb, it's all in gitosis-run-hook. --- gitosis/app.py | 73 +++++++++++++++++++++++++++++ gitosis/gitweb.py | 30 +----------- gitosis/init.py | 110 ++++++++++++++++++-------------------------- gitosis/run_hook.py | 86 +++++++++++----------------------- gitosis/serve.py | 95 +++++++++++++++----------------------- gitosis/ssh.py | 25 ---------- setup.py | 8 ++-- 7 files changed, 185 insertions(+), 242 deletions(-) create mode 100644 gitosis/app.py diff --git a/gitosis/app.py b/gitosis/app.py new file mode 100644 index 0000000..5ae796f --- /dev/null +++ b/gitosis/app.py @@ -0,0 +1,73 @@ +import os +import sys +import logging +import optparse +import ConfigParser + +class App(object): + name = None + + @classmethod + def run(class_): + app = class_() + return app.main() + + def main(self): + parser = self.create_parser() + (options, args) = parser.parse_args() + cfg = self.read_config(options) + self.setup_logging(cfg) + self.handle_args(parser, cfg, options, args) + + def create_parser(self): + parser = optparse.OptionParser() + parser.set_defaults( + config=os.path.expanduser('~/.gitosis.conf'), + ) + parser.add_option('--config', + metavar='FILE', + help='read config from FILE', + ) + + return parser + + def read_config(self, options): + cfg = ConfigParser.RawConfigParser() + try: + conffile = file(options.config) + except (IOError, OSError), e: + # I trust the exception has the path. + print >>sys.stderr, '%s: Unable to read config file: %s' \ + % (options.get_prog_name(), e) + sys.exit(1) + try: + cfg.readfp(conffile) + finally: + conffile.close() + return cfg + + def setup_logging(self, cfg): + logging.basicConfig() + + try: + loglevel = cfg.get('gitosis', 'loglevel') + except (ConfigParser.NoSectionError, + ConfigParser.NoOptionError): + pass + else: + try: + symbolic = logging._levelNames[loglevel] + except KeyError: + # need to delay error reporting until we've called + # basicConfig + log = logging.getLogger('gitosis.app') + log.warning( + 'Ignored invalid loglevel configuration: %r', + loglevel, + ) + else: + logging.root.setLevel(symbolic) + + def handle_args(self, parser, options, args): + if args: + parser.error('not expecting arguments') diff --git a/gitosis/gitweb.py b/gitosis/gitweb.py index 69f6c7d..1682a05 100644 --- a/gitosis/gitweb.py +++ b/gitosis/gitweb.py @@ -27,7 +27,7 @@ To plug this into ``gitweb``, you have two choices. import os, urllib, logging -from ConfigParser import RawConfigParser, NoSectionError, NoOptionError +from ConfigParser import NoSectionError, NoOptionError from gitosis import util @@ -113,31 +113,3 @@ def generate(config, path): f.close() os.rename(tmp, path) - -def _getParser(): - import optparse - parser = optparse.OptionParser( - usage="%prog [--config=FILE] PROJECTSLIST") - parser.set_defaults( - config=os.path.expanduser('~/.gitosis.conf'), - ) - parser.add_option('--config', - metavar='FILE', - help='read config from FILE (default %s)' - % parser.defaults['config'], - ) - return parser - -def main(): - parser = _getParser() - (options, args) = parser.parse_args() - - if len(args) != 1: - parser.error('Expected one command line argument.') - - path, = args - - cfg = RawConfigParser() - cfg.read(options.config) - - generate(config=cfg, path=path) diff --git a/gitosis/init.py b/gitosis/init.py index bf0eb9b..ba2e4c6 100644 --- a/gitosis/init.py +++ b/gitosis/init.py @@ -4,7 +4,6 @@ Initialize a user account for use with gitosis. import errno import logging -import optparse import os import re import subprocess @@ -16,13 +15,10 @@ from ConfigParser import RawConfigParser from gitosis import repository from gitosis import util +from gitosis import app log = logging.getLogger('gitosis.init') -def die(msg): - log.error(msg) - sys.exit(1) - def read_ssh_pubkey(fp=None): if fp is None: fp = sys.stdin @@ -55,6 +51,12 @@ def initial_commit(git_dir, cfg, pubkey, user): ], ) +class PostUpdateFailedError(Exception): + """post-update hook failed""" + + def __str__(self): + return '%s: %s' % (self.__doc__, ': '.join(self.args)) + def run_post_update(git_dir): args = [os.path.join(git_dir, 'hooks', 'post-update')] returncode = subprocess.call( @@ -64,10 +66,7 @@ def run_post_update(git_dir): env=dict(GIT_DIR='.'), ) if returncode != 0: - die( - ("post-update returned non-zero exit status %d" - % returncode), - ) + raise PostUpdateFailedError('exit status %d' % returncode) def symlink_config(git_dir): dst = os.path.expanduser('~/.gitosis.conf') @@ -85,20 +84,6 @@ def symlink_config(git_dir): ) os.rename(tmp, dst) -def getParser(): - parser = optparse.OptionParser( - usage='%prog', - description='Initialize a user account for use with gitosis', - ) - parser.set_defaults( - config=os.path.expanduser('~/.gitosis.conf'), - ) - parser.add_option('--config', - metavar='FILE', - help='read config from FILE', - ) - return parser - def init_admin_repository( git_dir, pubkey, @@ -130,49 +115,42 @@ def init_admin_repository( user=user, ) -def main(): - logging.basicConfig(level=logging.INFO) - os.umask(0022) +class Main(app.App): + def create_parser(self): + parser = super(Main, self).create_parser() + parser.set_usage('%prog [OPTS]') + parser.set_description( + 'Initialize a user account for use with gitosis') + return parser - parser = getParser() - (options, args) = parser.parse_args() - if args: - parser.error('Did not expect arguments.') + def handle_args(self, parser, cfg, options, args): + super(Main, self).handle_args(parser, cfg, options, args) - cfg = RawConfigParser() - try: - conffile = file(options.config) - except (IOError, OSError), e: - if e.errno == errno.ENOENT: - # not existing is ok - pass - else: - # I trust the exception has the path. - die("Unable to read config file: %s." % e) - else: + logging.basicConfig(level=logging.INFO) + os.umask(0022) + + log.info('Reading SSH public key...') + pubkey = read_ssh_pubkey() + user = ssh_extract_user(pubkey) + if user is None: + log.error('Cannot parse user from SSH public key.') + sys.exit(1) + log.info('Admin user is %r', user) + log.info('Creating repository structure...') + repositories = util.getRepositoryDir(cfg) + util.mkdir(repositories) + admin_repository = os.path.join(repositories, 'gitosis-admin.git') + init_admin_repository( + git_dir=admin_repository, + pubkey=pubkey, + user=user, + ) + log.info('Running post-update hook...') try: - cfg.readfp(conffile) - finally: - conffile.close() - - - log.info('Reading SSH public key...') - pubkey = read_ssh_pubkey() - user = ssh_extract_user(pubkey) - if user is None: - die('Cannot parse user from SSH public key.') - log.info('Admin user is %r', user) - log.info('Creating repository structure...') - repositories = util.getRepositoryDir(cfg) - util.mkdir(repositories) - admin_repository = os.path.join(repositories, 'gitosis-admin.git') - init_admin_repository( - git_dir=admin_repository, - pubkey=pubkey, - user=user, - ) - log.info('Running post-update hook...') - run_post_update(git_dir=admin_repository) - log.info('Symlinking ~/.gitosis.conf to repository...') - symlink_config(git_dir=admin_repository) - log.info('Done.') + run_post_update(git_dir=admin_repository) + except PostUpdateFailedError, e: + log.error('%s', e) + sys.exit(1) + log.info('Symlinking ~/.gitosis.conf to repository...') + symlink_config(git_dir=admin_repository) + log.info('Done.') diff --git a/gitosis/run_hook.py b/gitosis/run_hook.py index 2bc4aaa..de5076e 100644 --- a/gitosis/run_hook.py +++ b/gitosis/run_hook.py @@ -4,36 +4,14 @@ Perform gitosis actions for a git hook. import errno import logging -import optparse import os import sys import shutil -from ConfigParser import RawConfigParser - from gitosis import repository from gitosis import ssh from gitosis import gitweb - -log = logging.getLogger('gitosis.run_hook') - -def die(msg): - log.error(msg) - sys.exit(1) - -def getParser(): - parser = optparse.OptionParser( - usage='%prog HOOK', - description='Perform gitosis actions for a git hook', - ) - parser.set_defaults( - config=os.path.expanduser('~/.gitosis.conf'), - ) - parser.add_option('--config', - metavar='FILE', - help='read config from FILE', - ) - return parser +from gitosis import app def post_update(cfg): try: @@ -51,43 +29,33 @@ def post_update(cfg): keydir='gitosis-export/keydir', ) -def main(): - logging.basicConfig(level=logging.INFO) - os.umask(0022) +class Main(app.App): + def create_parser(self): + parser = super(Main, self).create_parser() + parser.set_usage('%prog [OPTS] HOOK') + parser.set_description( + 'Perform gitosis actions for a git hook') + return parser - parser = getParser() - (options, args) = parser.parse_args() - try: - (hook,) = args - except ValueError: - parser.error('Missing argument HOOK.') - - cfg = RawConfigParser() - try: - conffile = file(options.config) - except (IOError, OSError), e: - if e.errno == errno.ENOENT: - # not existing is ok - pass - else: - # I trust the exception has the path. - die("Unable to read config file: %s." % e) - else: + def handle_args(self, parser, cfg, options, args): try: - cfg.readfp(conffile) - finally: - conffile.close() + (hook,) = args + except ValueError: + parser.error('Missing argument HOOK.') - git_dir = os.environ.get('GIT_DIR') - if git_dir is None: - die('Must have GIT_DIR set in enviroment.') - os.chdir(git_dir) - os.environ['GIT_DIR'] = '.' + log = logging.getLogger('gitosis.run_hook') + os.umask(0022) - if hook == 'post-update': - log.info('Running hook %s', hook) - post_update(cfg) - log.info('Done.') - else: - log.warning('Ignoring unknown hook: %r', hook) - return 0 + git_dir = os.environ.get('GIT_DIR') + if git_dir is None: + log.error('Must have GIT_DIR set in enviroment') + sys.exit(1) + os.chdir(git_dir) + os.environ['GIT_DIR'] = '.' + + if hook == 'post-update': + log.info('Running hook %s', hook) + post_update(cfg) + log.info('Done.') + else: + log.warning('Ignoring unknown hook: %r', hook) diff --git a/gitosis/serve.py b/gitosis/serve.py index f47fe23..4b7c6a7 100644 --- a/gitosis/serve.py +++ b/gitosis/serve.py @@ -6,29 +6,11 @@ prefix. Repository names are forced to match ALLOW_RE. import logging -import sys, os, optparse, re -from ConfigParser import RawConfigParser +import sys, os, re from gitosis import access from gitosis import repository - -def die(msg): - print >>sys.stderr, '%s: %s' % (sys.argv[0], msg) - sys.exit(1) - -def getParser(): - parser = optparse.OptionParser( - usage='%prog [--config=FILE] USER', - description='Allow restricted git operations under DIR', - ) - parser.set_defaults( - config=os.path.expanduser('~/.gitosis.conf'), - ) - parser.add_option('--config', - metavar='FILE', - help='read config from FILE', - ) - return parser +from gitosis import app ALLOW_RE = re.compile("^'(?P[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$") @@ -124,48 +106,45 @@ def serve( ) return newcmd -def main(): - logging.basicConfig(level=logging.DEBUG) - log = logging.getLogger('gitosis.serve.main') - os.umask(0022) +class Main(app.App): + def create_parser(self): + parser = super(Main, self).create_parser() + parser.set_usage('%prog [OPTS] USER') + parser.set_description( + 'Allow restricted git operations under DIR') + return parser - parser = getParser() - (options, args) = parser.parse_args() - try: - (user,) = args - except ValueError: - parser.error('Missing argument USER.') + def handle_args(self, parser, cfg, options, args): + try: + (user,) = args + except ValueError: + parser.error('Missing argument USER.') - cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None) - if cmd is None: - die("Need SSH_ORIGINAL_COMMAND in environment.") + log = logging.getLogger('gitosis.serve.main') + os.umask(0022) - log.debug('Got command %(cmd)r' % dict( - cmd=cmd, - )) + cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None) + if cmd is None: + log.error('Need SSH_ORIGINAL_COMMAND in environment.') + sys.exit(1) - cfg = RawConfigParser() - try: - conffile = file(options.config) - except (IOError, OSError), e: - # I trust the exception has the path. - die("Unable to read config file: %s." % e) - try: - cfg.readfp(conffile) - finally: - conffile.close() + log.debug('Got command %(cmd)r' % dict( + cmd=cmd, + )) - os.chdir(os.path.expanduser('~')) + os.chdir(os.path.expanduser('~')) - try: - newcmd = serve( - cfg=cfg, - user=user, - command=cmd, - ) - except ServingError, e: - die(str(e)) + try: + newcmd = serve( + cfg=cfg, + user=user, + command=cmd, + ) + except ServingError, e: + log.error('%s', e) + sys.exit(1) - log.debug('Serving %s', newcmd) - os.execvpe('git-shell', ['git-shell', '-c', newcmd], {}) - die("Cannot execute git-shell.") + log.debug('Serving %s', newcmd) + os.execvpe('git-shell', ['git-shell', '-c', newcmd], {}) + log.error('Cannot execute git-shell.') + sys.exit(1) diff --git a/gitosis/ssh.py b/gitosis/ssh.py index 21f351a..7a70ed4 100644 --- a/gitosis/ssh.py +++ b/gitosis/ssh.py @@ -77,28 +77,3 @@ def writeAuthorizedKeys(path, keydir): if in_ is not None: in_.close() os.rename(tmp, path) - -def _getParser(): - import optparse - parser = optparse.OptionParser( - usage="%prog [--authkeys=FILE] KEYDIR") - parser.set_defaults( - authkeys=os.path.expanduser('~/.ssh/authorized_keys'), - ) - parser.add_option( - "--authkeys", - help="path to SSH authorized keys file") - return parser - -def main(): - parser = _getParser() - (options, args) = parser.parse_args() - - if len(args) != 1: - parser.error('Need one argument on the command line.') - - keydir, = args - - writeAuthorizedKeys( - path=options.authkeys, - keydir=keydir) diff --git a/setup.py b/setup.py index 50d1a6d..dd24285 100755 --- a/setup.py +++ b/setup.py @@ -30,11 +30,9 @@ setup( entry_points = { 'console_scripts': [ - 'gitosis-ssh = gitosis.ssh:main', - 'gitosis-serve = gitosis.serve:main', - 'gitosis-gitweb = gitosis.gitweb:main', - 'gitosis-run-hook = gitosis.run_hook:main', - 'gitosis-init = gitosis.init:main', + 'gitosis-serve = gitosis.serve:Main.run', + 'gitosis-run-hook = gitosis.run_hook:Main.run', + 'gitosis-init = gitosis.init:Main.run', ], },