import os from fabric.api import run, settings, hide, sudo from collections import defaultdict import yaml import re MIN_REVISION_LENGTH = 7 class PackageInfo: def __init__(self): path = os.path.abspath(__file__) with open(os.path.join( os.path.dirname(path), '../package_data.yaml')) as f: package_data = yaml.load(f) # exhaustive list of MITx repos self.repo_dirs = package_data['repo_dirs'] self.cmd_list = { 'pre': package_data['pre_checkout_regex'], 'post': package_data['post_checkout_regex']} self.service_repos = package_data['service_repos'] def repo_from_name(self, name): repos = [] for repo_root in self.repo_dirs: if os.path.basename(repo_root) == name: repos.append(self.repo_dirs[repo_root]) if len(repos) > 1: raise Exception['Multiple repos found for name'] elif len(repos) == 0: raise Exception['Repo not found for name'] else: return repos[0].split('/')[1] def org_from_name(self, name): repos = [] for repo_root in self.repo_dirs: if os.path.basename(repo_root) == name: repos.append(self.repo_dirs[repo_root]) if len(repos) > 1: raise Exception['Multiple repos found for name'] elif len(repos) == 0: raise Exception['Repo not found for name'] else: return repos[0].split('/')[0] def pre_post_actions(self, pkgs): """ Returns a dictionary containing a list of commands that need to be executed pre and post checkout for one or more package names. return({ 'pre': [ 'cmd1', 'cmd2', ... ], 'post': [ 'cmd1', 'cmd2', ... ] }) """ cmds = defaultdict(list) for stage in ['pre', 'post']: for regex, cmd_templates in self.cmd_list[stage]: for pkg in pkgs: match = re.match(regex, pkg) if match is None: continue cmds[stage].extend( cmd.format(*match.groups(), **match.groupdict()) for cmd in cmd_templates if cmd not in cmds[stage] ) return(cmds) def installed_packages(self): """ Returns the list of PackageDescriptors for the packages installed on the system. This is determined by looking at every package directory we know about and checking its revision. """ with settings(hide('running'), warn_only=True): revisions = sudo( """ for path in {0}; do if [[ -d "$path/.git" ]]; then echo $path $(cd $path && git rev-parse HEAD 2>/dev/null) fi done """.format(' '.join(self.repo_dirs))).split('\n') packages = [revline.strip().split(' ') for revline in revisions if ' ' in revline.strip()] return [PackageDescriptor(os.path.basename(path), revision) for path, revision in packages] class PackageDescriptor(object): def __init__(self, name, revision): if revision != 'absent' and len(revision) < MIN_REVISION_LENGTH: raise Exception("Must use at least {0} characters " "in revision to pseudo-guarantee uniqueness".format( MIN_REVISION_LENGTH)) self.name = name # Find the repo_root by name # This assumes that basename(repo_root) is unique # for all repo_roots. If this is not true an exception # will be raised pkg_info = PackageInfo() repo_roots = [] for repo_dir in pkg_info.repo_dirs.keys(): if os.path.basename(repo_dir) == name: repo_roots.append(repo_dir) if len(repo_roots) != 1: raise Exception("Unable to look up directory for repo") self.repo_root = repo_roots[0] self.repo_name = pkg_info.repo_dirs[self.repo_root].split('/')[1] self.repo_org = pkg_info.repo_dirs[self.repo_root].split('/')[0] self.revision = revision