"""Invirt build utilities. This module contains utility functions used by both the invirtibuilder and the remctl submission scripts that insert items into its queue. """ import os from debian_bundle import changelog from debian_bundle import deb822 import invirt.common as c from invirt.config import structs as config _QUEUE_DIR = '/var/lib/invirt-dev/queue' _REPO_DIR = '/srv/git' _LOG_DIR = '/var/log/invirt/builds' _HOOKS_DIR = '/usr/share/invirt-dev/build.d' class InvalidBuild(ValueError): pass def getRepo(package): """Return the path to the git repo for a given package.""" return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package) def ensureValidRepo(package): """Perform some basic sanity checks that the requested repo is in a subdirectory of _REPO_DIR/invirt/packages. This prevents weirdness such as submitting a package like '../prod/...git'. Also ensures that the repo exists.""" # TODO: this might be easier just to regex repo = os.path.abspath(getRepo(package)) parent_dir = os.path.dirname(repo) prefix = os.path.join(_REPO_DIR, 'invirt/packages') if not parent_dir.startswith(prefix): raise InvalidBuild('Invalid package name %s' % package) elif not os.path.exists(repo): raise InvalidBuild('Nonexisting package %s' % package) def pocketToGit(pocket): """Map a pocket in the configuration to a git branch.""" return getattr(getattr(config.build.pockets, pocket), 'git', pocket) def pocketToApt(pocket): """Map a pocket in the configuration to an apt repo pocket.""" return getattr(getattr(config.build.pockets, pocket), 'apt', pocket) def getGitFile(package, ref, path): """Return the contents of a path from a git ref in a package.""" return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)], cwd=getRepo(package)) def getChangelog(package, ref): """Get a changelog object for a given ref in a given package. This returns a debian_bundle.changelog.Changelog object for a given ref of a given package. """ return changelog.Changelog(getGitFile(package, ref, 'debian/changelog')) def getVersion(package, ref): """Get the version of a given package at a particular ref.""" return getChangelog(package, ref).get_version() def validateBuild(pocket, package, commit): """Given the parameters of a new build, validate that build. The checks this function performs vary based on whether or not the pocket is configured with allow_backtracking. A build of a pocket without allow_backtracking set must be a fast-forward of the previous revision, and the most recent version in the changelog most be strictly greater than the version currently in the repository. In all cases, this revision of the package can only have the same version number as any other revision currently in the apt repository if they have the same commit ID. If it's unspecified, it is assumed that pocket do not allow_backtracking. If this build request fails validation, this function will raise a InvalidBuild exception, with information about why the validation failed. If this build request can be satisfied by copying the package from another pocket, then this function returns that pocket. Otherwise, it returns True. """ ensureValidRepo(package) package_repo = getRepo(package) new_version = getVersion(package, commit) ret = True for p in config.build.pockets: if p == pocket: continue b = pocketToGit(p) current_commit = c.captureOutput(['git', 'rev-parse', b], cwd=package_repo).strip() current_version = getVersion(package, b) if current_version == new_version: if current_commit == commit: ret = p else: raise InvalidBuild('Version %s of %s already available is in ' 'pocket %s from commit %s' % (new_version, package, p, current_commit)) if not config.build.pockets[pocket].get('allow_backtracking', False): branch = pocketToGit(pocket) current_version = getVersion(package, branch) if new_version <= current_version: raise InvalidBuild('New version %s of %s is not newer than ' 'version %s currently in pocket %s' % (new_version, package, current_version, pocket)) # Almost by definition, A is a fast-forward of B if B..A is # empty if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)]): raise InvalidBuild('New commit %s of %s is not a fast-forward of' 'commit currently in pocket %s' % (commit, package, pocket)) return ret