Manage git-daemon-export-ok flags from gitosis config.
This commit is contained in:
parent
2487c658ba
commit
13c89cdb7d
5 changed files with 253 additions and 5 deletions
3
TODO.rst
3
TODO.rst
|
@ -6,7 +6,8 @@
|
|||
|
||||
- gitosis-lint: check that the user account (e.g. ``git``) looks valid
|
||||
|
||||
- git-daemon-export-ok
|
||||
- create git-daemon-export-ok, description, projects.list etc when
|
||||
autocreating repositorites?
|
||||
|
||||
- guard against *.pub files named -foo.pub or foo;bar.pub
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@ gitweb = no
|
|||
|
||||
## Allow git-daemon to publish all known repositories. As with gitweb,
|
||||
## this can be done globally or per-repository.
|
||||
## NOT YET IMPLEMENTED.
|
||||
# daemon-ok = no
|
||||
daemon = no
|
||||
|
||||
## Logging level, one of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
loglevel = DEBUG
|
||||
|
@ -41,8 +40,7 @@ gitweb = yes
|
|||
owner = John Doe
|
||||
|
||||
## Allow git-daemon to publish this repository.
|
||||
## NOT YET IMPLEMENTED.
|
||||
# daemon-ok = no
|
||||
daemon = yes
|
||||
|
||||
[gitweb]
|
||||
## Where to make gitweb link to as it's "home location".
|
||||
|
|
90
gitosis/gitdaemon.py
Normal file
90
gitosis/gitdaemon.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
||||
from ConfigParser import NoSectionError, NoOptionError
|
||||
|
||||
log = logging.getLogger('gitosis.gitdaemon')
|
||||
|
||||
from gitosis import util
|
||||
|
||||
def export_ok_path(repopath):
|
||||
p = os.path.join(repopath, 'git-daemon-export-ok')
|
||||
return p
|
||||
|
||||
def allow_export(repopath):
|
||||
p = export_ok_path(repopath)
|
||||
file(p, 'a').close()
|
||||
|
||||
def deny_export(repopath):
|
||||
p = export_ok_path(repopath)
|
||||
try:
|
||||
os.unlink(p)
|
||||
except OSError, e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
def _extract_reldir(topdir, dirpath):
|
||||
if topdir == dirpath:
|
||||
return '.'
|
||||
prefix = topdir + '/'
|
||||
assert dirpath.startswith(prefix)
|
||||
reldir = dirpath[len(prefix):]
|
||||
return reldir
|
||||
|
||||
def set_export_ok(config):
|
||||
repositories = util.getRepositoryDir(config)
|
||||
|
||||
try:
|
||||
global_enable = config.getboolean('gitosis', 'daemon')
|
||||
except (NoSectionError, NoOptionError):
|
||||
global_enable = False
|
||||
log.debug(
|
||||
'Global default is %r',
|
||||
{True: 'allow', False: 'deny'}.get(global_enable),
|
||||
)
|
||||
|
||||
def _error(e):
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
|
||||
for (dirpath, dirnames, filenames) \
|
||||
in os.walk(repositories, onerror=_error):
|
||||
# oh how many times i have wished for os.walk to report
|
||||
# topdir and reldir separately, instead of dirpath
|
||||
reldir = _extract_reldir(
|
||||
topdir=repositories,
|
||||
dirpath=dirpath,
|
||||
)
|
||||
|
||||
log.debug('Walking %r, seeing %r', reldir, dirnames)
|
||||
|
||||
to_recurse = []
|
||||
repos = []
|
||||
for dirname in dirnames:
|
||||
if dirname.endswith('.git'):
|
||||
repos.append(dirname)
|
||||
else:
|
||||
to_recurse.append(dirname)
|
||||
dirnames[:] = to_recurse
|
||||
|
||||
for repo in repos:
|
||||
name, ext = os.path.splitext(repo)
|
||||
if reldir != '.':
|
||||
name = os.path.join(reldir, name)
|
||||
assert ext == '.git'
|
||||
try:
|
||||
enable = config.getboolean('repo %s' % name, 'daemon')
|
||||
except (NoSectionError, NoOptionError):
|
||||
enable = global_enable
|
||||
|
||||
if enable:
|
||||
log.debug('Allow %r', name)
|
||||
allow_export(os.path.join(dirpath, repo))
|
||||
else:
|
||||
log.debug('Deny %r', name)
|
||||
deny_export(os.path.join(dirpath, repo))
|
|
@ -11,6 +11,7 @@ import shutil
|
|||
from gitosis import repository
|
||||
from gitosis import ssh
|
||||
from gitosis import gitweb
|
||||
from gitosis import gitdaemon
|
||||
from gitosis import app
|
||||
|
||||
def post_update(cfg, git_dir):
|
||||
|
@ -31,6 +32,9 @@ def post_update(cfg, git_dir):
|
|||
config=cfg,
|
||||
path=os.path.join(git_dir, 'projects.list'),
|
||||
)
|
||||
gitdaemon.set_export_ok(
|
||||
config=cfg,
|
||||
)
|
||||
ssh.writeAuthorizedKeys(
|
||||
path=os.path.expanduser('~/.ssh/authorized_keys'),
|
||||
keydir=os.path.join(export, 'keydir'),
|
||||
|
|
155
gitosis/test/test_gitdaemon.py
Normal file
155
gitosis/test/test_gitdaemon.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
from nose.tools import eq_ as eq
|
||||
|
||||
import os
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
from gitosis import gitdaemon
|
||||
from gitosis.test.util import maketemp, writeFile
|
||||
|
||||
def exported(path):
|
||||
assert os.path.isdir(path)
|
||||
p = gitdaemon.export_ok_path(path)
|
||||
return os.path.exists(p)
|
||||
|
||||
def test_git_daemon_export_ok_repo_missing():
|
||||
# configured but not created yet; before first push
|
||||
tmp = maketemp()
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
cfg.set('repo foo', 'daemon', 'yes')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
assert not os.path.exists(os.path.join(tmp, 'foo'))
|
||||
assert not os.path.exists(os.path.join(tmp, 'foo.git'))
|
||||
|
||||
def test_git_daemon_export_ok_repo_missing_parent():
|
||||
# configured but not created yet; before first push
|
||||
tmp = maketemp()
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo/bar')
|
||||
cfg.set('repo foo/bar', 'daemon', 'yes')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
assert not os.path.exists(os.path.join(tmp, 'foo'))
|
||||
|
||||
def test_git_daemon_export_ok_allowed():
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
cfg.set('repo foo', 'daemon', 'yes')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), True)
|
||||
|
||||
def test_git_daemon_export_ok_allowed_already():
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
writeFile(gitdaemon.export_ok_path(path), '')
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
cfg.set('repo foo', 'daemon', 'yes')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), True)
|
||||
|
||||
def test_git_daemon_export_ok_denied():
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
writeFile(gitdaemon.export_ok_path(path), '')
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
cfg.set('repo foo', 'daemon', 'no')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), False)
|
||||
|
||||
def test_git_daemon_export_ok_denied_already():
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
cfg.set('repo foo', 'daemon', 'no')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), False)
|
||||
|
||||
def test_git_daemon_export_ok_subdirs():
|
||||
tmp = maketemp()
|
||||
foo = os.path.join(tmp, 'foo')
|
||||
os.mkdir(foo)
|
||||
path = os.path.join(foo, 'bar.git')
|
||||
os.mkdir(path)
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo/bar')
|
||||
cfg.set('repo foo/bar', 'daemon', 'yes')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), True)
|
||||
|
||||
def test_git_daemon_export_ok_denied_default():
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
writeFile(gitdaemon.export_ok_path(path), '')
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.add_section('repo foo')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), False)
|
||||
|
||||
def test_git_daemon_export_ok_denied_even_not_configured():
|
||||
# repositories not mentioned in config also get touched; this is
|
||||
# to avoid security trouble, otherwise we might expose (or
|
||||
# continue to expose) old repositories removed from config
|
||||
tmp = maketemp()
|
||||
path = os.path.join(tmp, 'foo.git')
|
||||
os.mkdir(path)
|
||||
writeFile(gitdaemon.export_ok_path(path), '')
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(path), False)
|
||||
|
||||
def test_git_daemon_export_ok_allowed_global():
|
||||
tmp = maketemp()
|
||||
|
||||
for repo in [
|
||||
'foo.git',
|
||||
'quux.git',
|
||||
'thud.git',
|
||||
]:
|
||||
path = os.path.join(tmp, repo)
|
||||
os.mkdir(path)
|
||||
|
||||
# try to provoke an invalid allow
|
||||
writeFile(gitdaemon.export_ok_path(os.path.join(tmp, 'thud.git')), '')
|
||||
|
||||
cfg = RawConfigParser()
|
||||
cfg.add_section('gitosis')
|
||||
cfg.set('gitosis', 'repositories', tmp)
|
||||
cfg.set('gitosis', 'daemon', 'yes')
|
||||
cfg.add_section('repo foo')
|
||||
cfg.add_section('repo quux')
|
||||
# same as default, no effect
|
||||
cfg.set('repo quux', 'daemon', 'yes')
|
||||
cfg.add_section('repo thud')
|
||||
# this is still hidden
|
||||
cfg.set('repo thud', 'daemon', 'no')
|
||||
gitdaemon.set_export_ok(config=cfg)
|
||||
eq(exported(os.path.join(tmp, 'foo.git')), True)
|
||||
eq(exported(os.path.join(tmp, 'quux.git')), True)
|
||||
eq(exported(os.path.join(tmp, 'thud.git')), False)
|
Loading…
Reference in a new issue