aboutsummaryrefslogtreecommitdiff
path: root/grok-cron.py
blob: bd9a57c364eb8f9f394d57b46486f656829de147 (plain)
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
#!/usr/bin/python

import argparse
import logging
import os
import subprocess

import grokmirror

EXCLUDES = (
    'gitolite-admin.git',
)


logging.basicConfig(level=logging.INFO)
log = logging.getLogger('grok-cron')


def _can_mirror(full_path):
    # we have a couple of old symlinks set up, that we don't have
    # to manage via grokmirror
    if os.path.isdir(full_path) and not os.path.islink(full_path):
        if os.path.exists(os.path.join(full_path, 'noweb')):
            return False
        if len(os.listdir(os.path.join(full_path, 'refs/heads'))) == 0:
            # grokmirror won't add these to the manifest (although it should)
            # these are typicall Gerrit project holders with:
            #   HEAD=refs/meta/config
            return False
        return True
    return False


def handle_unmanaged(manifile, manifest_repos, repo_dir, dryrun):
    '''Handle repos that have been moved and not picked up by grokmirror.
    This can happen when ITS moves a repo from one user's account to another
    during an exit procedure.
    '''
    if not os.path.exists('/home/git/bin/gitolite'):
        log.debug('Gitolite not installed, skipping unmanaged repo check')
        return

    missing = []
    repos = subprocess.check_output(
        ['/home/git/bin/gitolite', 'list-phy-repos'])
    for repo in repos.splitlines():
        repo = repo.strip() + '.git'
        full_path = os.path.join(repo_dir, repo)
        if _can_mirror(full_path):
            if repo not in manifest_repos and repo not in EXCLUDES:
                missing.append(repo)

    # grok-manifest is particular about the path format
    if repo_dir[-1] != '/':
        repo_dir += '/'
        logging.debug('fixed repo path: %s', repo_dir)
    for repo in missing:
        log.info('Adding %s to manifest', repo)
        if not dryrun:
            args = [
                '/usr/local/bin/grok-manifest',
                '-m', manifile, '-t', repo_dir, '-n', repo,
            ]
            subprocess.check_call(args, cwd=repo_dir)


def main(manifile, repo_dir, no_check_export, dryrun):
    # uses advisory lock, so its safe even if we die unexpectedly
    grokmirror.manifest_lock(manifile)

    changed = False
    manifest = grokmirror.read_manifest(manifile)
    for repo in manifest.keys():
        # grokmirror doesn't play well with leading / and it also breaks
        # os.path.join, so just break loudly if this happens (which based
        # on our ansible deployment is impossible)
        log.debug('Checking for %s', repo)
        assert repo[0] != '/'

        g = os.path.join(repo_dir, repo)
        gde = os.path.join(g, 'git-daemon-export-ok')

        if not os.path.exists(g) or \
                (not no_check_export and not os.path.exists(gde)):
            log.info("Removing %s from manifest...", repo)
            del manifest[repo]
            changed = True

    if changed and not dryrun:
        grokmirror.write_manifest(manifile, manifest)
    grokmirror.manifest_unlock(manifile)

    # do this outside the scope of the manifest lock, to avoid a deadlock
    handle_unmanaged(manifile, manifest.keys(), repo_dir, dryrun)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='''Handle tasks that cannot be done in gitolite hooks like
                    purging deleting repos or updating descriptions.''')
    parser.add_argument('-m', '--manifest', required=True,
                        help='grok manifest file')
    parser.add_argument('-t', '--toplevel', required=True,
                        help='location of repositories')
    parser.add_argument('--no-check-export', action='store_true',
                        help='git-daemon-export-ok file not required')
    parser.add_argument('-n', '--dryrun', action='store_true',
                        help='Make no changes to manifest')
    parser.add_argument('--log', default='WARN',
                        choices=('WARN', 'INFO', 'DEBUG'),
                        help='Logging level to use. Default=%(default)s')
    args = parser.parse_args()

    for l in logging.getLogger().manager.loggerDict.keys():
        logging.getLogger(l).setLevel(getattr(logging, args.log))

    main(args.manifest, args.toplevel, args.no_check_export, args.dryrun)