1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
#!/usr/bin/python3
import logging
import logging.handlers
import os
import shlex
import subprocess
import sys
import time
import urllib.parse
import configparser
log = logging.getLogger('grok-shell')
def setup_logging():
log.setLevel(logging.INFO)
# rotate logs every sunday, and retain for 5 weeks
handler = logging.handlers.TimedRotatingFileHandler(
'/tmp/grok-shell.log', when='W6', backupCount=5)
formatter = logging.Formatter(
'%(asctime)s %(levelname)-5s %(name)s: %(message)s',
datefmt='%H:%M:%S')
handler.setFormatter(formatter)
log.addHandler(handler)
def _slave_local_fetch(cmd):
cmd, repo = cmd.split(' ', 1)
# git-shell command has an undocumented requirement that the repo be
# wrapped in quotes
assert repo[0] == "'" and repo[-1] == "'"
repo = "'/srv/repositories/" + repo[1:]
args = ['/usr/bin/git-shell', '-c', "%s %s" % (cmd, repo)]
os.execv(args[0], args)
def _get_master():
ini = configparser.ConfigParser()
ini.read('/etc/grokmirror-repos.conf')
master = ini.get(ini.sections()[0], 'site')
return urllib.parse.urlparse(master).netloc
def _slave_remote_cmd(user, cmd):
# we have to use posix=False or shlex will try and strip out quotes for
# an arg like 'people/foo.bar/repo'. The quotes are required by git-shell
# as noted in _slave_local_fetch
cmd = shlex.split(cmd, posix=False)
args = ['/usr/bin/ssh', _get_master(), user] + cmd
start = time.time()
ret = subprocess.call(args)
if ret == 0 and args[3] == 'git-receive-pack':
log.info('remote update for %s %s took %d ms',
user, cmd, time.time() - start)
start = time.time()
# help make the change show up on the local grok slave as quick as
# possible, this won't update the grok-manifest, but our cron job will
# take care of that when it runs next.
repo = args[4]
assert repo[0] == "'" and repo[-1] == "'"
repo = '/srv/repositories/' + repo[1:-1]
if not repo.endswith('.git'):
repo += '.git'
# only do updates to existing repos, let cron job handle clones
if os.path.exists(repo):
os.write(2, b'Updating local grokmirror with changes on master\n')
subprocess.call(
['/usr/bin/git', 'remote', 'update', '--prune'], cwd=repo)
log.info('local update for %s %s took %d ms',
user, cmd, time.time() - start)
sys.exit(ret)
def _master_shell(cmd):
user, cmd = cmd.split(' ', 1)
os.environ['SSH_ORIGINAL_COMMAND'] = cmd
os.environ['SSH_CONNECTION'] = '1'
args = ['/home/git/gitolite/src/gitolite-shell', user]
os.execv(args[0], args)
if __name__ == '__main__':
'''This script is called in 2 different ways:
1) on grokmirror slave, it will determine if we are doing a read or write
operation. If a read, we'll keep the request local. If a write, we'll
send a request to the master to complete
2) on grokmirror master, it will accept the remote command from #1 and
handle its request.
Both slave and master require special .ssh/authorzied_key entries to be
in place.
'''
setup_logging()
cmd = os.environ.get('SSH_ORIGINAL_COMMAND', 'info')
if len(sys.argv) < 2 or sys.argv[1] not in ('slave', 'master'):
sys.exit('Usage: %s [slave|master] ...' % sys.argv[0])
shell_type = sys.argv[1]
if shell_type == 'master':
log.info('command from slave: %s', cmd)
_master_shell(cmd)
elif shell_type == 'slave':
user = sys.argv[2]
if cmd.startswith('git-upload-'):
log.info('%s: local: %s', user, cmd)
_slave_local_fetch(cmd)
else:
log.info('%s: remote: %s', user, cmd)
_slave_remote_cmd(user, cmd)
|