"""
Initialize a user account for use with gitosis.
"""
import errno
import logging
import optparse
import os
import re
import subprocess
import sys
from pkg_resources import resource_filename
from cStringIO import StringIO
from ConfigParser import RawConfigParser
from gitosis import repository
from gitosis import util
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
line = fp.readline()
return line
_ACCEPTABLE_USER_RE = re.compile(r'^[a-z][a-z0-9]*@[a-z][a-z0-9]*$')
class InsecureSSHKeyUsername(Exception):
"""Username contains not allowed characters"""
def __str__(self):
return '%s: %s' % (self.__doc__, ': '.join(self.args))
def ssh_extract_user(pubkey):
_, user = pubkey.rsplit(None, 1)
if _ACCEPTABLE_USER_RE.match(user):
return user
else:
raise InsecureSSHKeyUsername(repr(user))
def initial_commit(git_dir, cfg, pubkey, user):
repository.fast_import(
git_dir=git_dir,
commit_msg='Automatic creation of gitosis repository.',
committer='Gitosis Admin <%s>' % user,
files=[
('keydir/%s.pub' % user, pubkey),
('gitosis.conf', cfg),
],
)
def run_post_update(git_dir):
args = [os.path.join(git_dir, 'hooks', 'post-update')]
returncode = subprocess.call(
args=args,
cwd=git_dir,
close_fds=True,
env=dict(GIT_DIR='.'),
)
if returncode != 0:
die(
("post-update returned non-zero exit status %d"
% returncode),
)
def symlink_config(git_dir):
dst = os.path.expanduser('~/.gitosis.conf')
tmp = '%s.%d.tmp' % (dst, os.getpid())
try:
os.unlink(tmp)
except OSError, e:
if e.errno == errno.ENOENT:
pass
else:
raise
os.symlink(
os.path.join(git_dir, 'gitosis.conf'),
tmp,
)
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,
user,
):
repository.init(
path=git_dir,
template=resource_filename('gitosis.templates', 'admin')
)
repository.init(
path=git_dir,
)
if not repository.has_initial_commit(git_dir):
log.info('Making initial commit...')
cfg_file = StringIO()
print >>cfg_file, '[gitosis]'
print >>cfg_file
cfg = RawConfigParser()
cfg.add_section('group gitosis-admin')
cfg.set('group gitosis-admin', 'members', user)
cfg.set('group gitosis-admin', 'writable', 'gitosis-admin')
cfg.write(cfg_file)
initial_commit(
git_dir=git_dir,
cfg=cfg_file.getvalue(),
pubkey=pubkey,
user=user,
)
def main():
logging.basicConfig(level=logging.INFO)
os.umask(0022)
parser = getParser()
(options, args) = parser.parse_args()
if args:
parser.error('Did not expect arguments.')
cfg = RawConfigParser()
try:
conffile = file(options.config)
except (IOError, OSError), e:
if e.errno == errno.ENOENT:
pass
else:
die("Unable to read config file: %s." % e)
else:
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.')