[2543] | 1 | """Invirt build utilities. |
---|
| 2 | |
---|
| 3 | This module contains utility functions used by both the invirtibuilder |
---|
| 4 | and the remctl submission scripts that insert items into its queue. |
---|
| 5 | """ |
---|
| 6 | |
---|
| 7 | |
---|
| 8 | import os |
---|
| 9 | |
---|
| 10 | from debian_bundle import changelog |
---|
| 11 | from debian_bundle import deb822 |
---|
| 12 | |
---|
| 13 | import invirt.common as c |
---|
| 14 | from invirt.config import structs as config |
---|
| 15 | |
---|
| 16 | |
---|
| 17 | _QUEUE_DIR = '/var/lib/invirt-dev/queue' |
---|
| 18 | _REPO_DIR = '/srv/git' |
---|
| 19 | _LOG_DIR = '/var/log/invirt/builds' |
---|
| 20 | _HOOKS_DIR = '/usr/share/invirt-dev/build.d' |
---|
| 21 | |
---|
| 22 | |
---|
| 23 | class InvalidBuild(ValueError): |
---|
| 24 | pass |
---|
| 25 | |
---|
| 26 | |
---|
| 27 | def getRepo(package): |
---|
| 28 | """Return the path to the git repo for a given package.""" |
---|
[2577] | 29 | return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package) |
---|
[2543] | 30 | |
---|
| 31 | |
---|
| 32 | def pocketToGit(pocket): |
---|
| 33 | """Map a pocket in the configuration to a git branch.""" |
---|
| 34 | return config.git.pockets[pocket].get('git', pocket) |
---|
| 35 | |
---|
| 36 | |
---|
| 37 | def pocketToApt(pocket): |
---|
| 38 | """Map a pocket in the configuration to an apt repo pocket.""" |
---|
| 39 | return config.git.pockets[pocket].get('apt', pocket) |
---|
| 40 | |
---|
| 41 | |
---|
| 42 | def getGitFile(package, ref, path): |
---|
| 43 | """Return the contents of a path from a git ref in a package.""" |
---|
| 44 | return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)], |
---|
| 45 | cwd=getRepo(package)) |
---|
| 46 | |
---|
| 47 | |
---|
| 48 | def getChangelog(package, ref): |
---|
| 49 | """Get a changelog object for a given ref in a given package. |
---|
| 50 | |
---|
| 51 | This returns a debian_bundle.changelog.Changelog object for a |
---|
| 52 | given ref of a given package. |
---|
| 53 | """ |
---|
| 54 | return changelog.Changelog(getGitFile(package, ref, 'debian/changelog')) |
---|
| 55 | |
---|
| 56 | |
---|
| 57 | def getVersion(package, ref): |
---|
| 58 | """Get the version of a given package at a particular ref.""" |
---|
| 59 | return getChangelog(package, ref).get_version() |
---|
| 60 | |
---|
| 61 | |
---|
| 62 | def validateBuild(pocket, package, commit): |
---|
| 63 | """Given the parameters of a new build, validate that build. |
---|
| 64 | |
---|
| 65 | The checks this function performs vary based on whether or not the |
---|
| 66 | pocket is configured with allow_backtracking. |
---|
| 67 | |
---|
| 68 | A build of a pocket without allow_backtracking set must be a |
---|
| 69 | fast-forward of the previous revision, and the most recent version |
---|
| 70 | in the changelog most be strictly greater than the version |
---|
| 71 | currently in the repository. |
---|
| 72 | |
---|
| 73 | In all cases, this revision of the package can only have the same |
---|
| 74 | version number as any other revision currently in the apt |
---|
| 75 | repository if they have the same commit ID. |
---|
| 76 | |
---|
| 77 | If it's unspecified, it is assumed that pocket do not |
---|
| 78 | allow_backtracking. |
---|
| 79 | |
---|
| 80 | If this build request fails validation, this function will raise a |
---|
| 81 | InvalidBuild exception, with information about why the validation |
---|
| 82 | failed. |
---|
| 83 | |
---|
| 84 | If this build request can be satisfied by copying the package from |
---|
| 85 | another pocket, then this function returns that pocket. Otherwise, |
---|
| 86 | it returns True. |
---|
| 87 | """ |
---|
| 88 | package_repo = getRepo(package) |
---|
| 89 | new_version = getVersion(package, commit) |
---|
| 90 | |
---|
| 91 | for p in config.git.pockets: |
---|
| 92 | if p == pocket: |
---|
| 93 | continue |
---|
| 94 | |
---|
| 95 | b = pocketToGit(p) |
---|
| 96 | current_commit = c.captureOutput(['git', 'rev-parse', b], |
---|
| 97 | cwd=package_repo) |
---|
| 98 | current_version = getVersion(package, b) |
---|
| 99 | |
---|
| 100 | if current_version == new_version: |
---|
| 101 | if current_commit == commit: |
---|
| 102 | return p |
---|
| 103 | else: |
---|
| 104 | raise InvalidBuild('Version %s of %s already available in ' |
---|
| 105 | 'pocket %s from commit %s' % |
---|
| 106 | (new_version, package, p, current_commit)) |
---|
| 107 | |
---|
| 108 | if config.git.pockets[pocket].get('allow_backtracking', False): |
---|
| 109 | branch = pocketToGit(pocket) |
---|
| 110 | current_version = getVersion(package, branch) |
---|
| 111 | if new_version <= current_version: |
---|
| 112 | raise InvalidBuild('New version %s of %s is not newer than ' |
---|
| 113 | 'version %s currently in pocket %s' % |
---|
| 114 | (new_version, package, current_version, pocket)) |
---|
| 115 | |
---|
| 116 | # Almost by definition, A is a fast-forward of B if B..A is |
---|
| 117 | # empty |
---|
| 118 | if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)]): |
---|
| 119 | raise InvalidBuild('New commit %s of %s is not a fast-forward of' |
---|
| 120 | 'commit currently in pocket %s' % |
---|
| 121 | (commit, package, pocket)) |
---|
| 122 | |
---|
| 123 | |
---|