1 """Invirt build utilities.
3 This module contains utility functions used by both the invirtibuilder
4 and the remctl submission scripts that insert items into its queue.
11 from debian_bundle import changelog
13 import invirt.common as c
14 from invirt.config import structs as config
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-hooks'
23 class InvalidBuild(ValueError):
28 """Return the path to the git repo for a given package."""
29 return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package)
31 def ensureValidPackage(package):
32 """Perform some basic sanity checks that the requested repo is in a
33 subdirectory of _REPO_DIR/invirt/packages. This prevents weirdness
34 such as submitting a package like '../prod/...git'. Also ensures that
36 # TODO: this might be easier just to regex
37 repo = os.path.abspath(getRepo(package))
38 parent_dir = os.path.dirname(repo)
39 prefix = os.path.join(_REPO_DIR, 'invirt/packages')
40 if not parent_dir.startswith(prefix):
41 raise InvalidBuild('Invalid package name %s' % package)
42 elif not os.path.exists(repo):
43 raise InvalidBuild('Nonexisting package %s' % package)
45 def canonicalize_commit(package, commit, shorten=False):
50 return c.captureOutput(['git', 'rev-parse'] + flags + [commit],
51 cwd=getRepo(package)).strip()
53 def pocketToGit(pocket):
54 """Map a pocket in the configuration to a git branch."""
55 return getattr(getattr(config.build.pockets, pocket), 'git', pocket)
58 def pocketToApt(pocket):
59 """Map a pocket in the configuration to an apt repo pocket."""
60 return getattr(getattr(config.build.pockets, pocket), 'apt', pocket)
63 def getGitFile(package, ref, path):
64 """Return the contents of a path from a git ref in a package."""
65 return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)],
69 def getChangelog(package, ref):
70 """Get a changelog object for a given ref in a given package.
72 This returns a debian_bundle.changelog.Changelog object for a
73 given ref of a given package.
75 return changelog.Changelog(getGitFile(package, ref, 'debian/changelog'))
77 def runHook(hook, args=[], stdin_str=None):
78 """Run a named hook."""
79 hook = os.path.join(_HOOKS_DIR, hook)
81 c.captureOutput([hook] + args, stdin_str=stdin_str)
85 def getVersion(package, ref):
86 """Get the version of a given package at a particular ref."""
87 return getChangelog(package, ref).get_version()
90 def validateBuild(pocket, package, commit):
91 """Given the parameters of a new build, validate that build.
93 The checks this function performs vary based on whether or not the
94 pocket is configured with allow_backtracking.
96 A build of a pocket without allow_backtracking set must be a
97 fast-forward of the previous revision, and the most recent version
98 in the changelog most be strictly greater than the version
99 currently in the repository.
101 In all cases, this revision of the package can only have the same
102 version number as any other revision currently in the apt
103 repository if they have the same commit ID.
105 If it's unspecified, it is assumed that pocket do not
108 If this build request fails validation, this function will raise a
109 InvalidBuild exception, with information about why the validation
112 If this build request can be satisfied by copying the package from
113 another pocket, then this function returns that pocket. Otherwise,
116 ensureValidPackage(package)
117 package_repo = getRepo(package)
118 new_version = getVersion(package, commit)
122 for p in config.build.pockets:
128 current_commit = c.captureOutput(['git', 'rev-parse', b],
129 cwd=package_repo).strip()
130 except subprocess.CalledProcessError:
131 # Guess we haven't created this pocket yet
134 current_version = getVersion(package, b)
136 if current_version == new_version:
137 if current_commit == commit:
140 raise InvalidBuild('Version %s of %s already available is in '
141 'pocket %s from commit %s' %
142 (new_version, package, p, current_commit))
144 if not config.build.pockets[pocket].get('allow_backtracking', False):
145 branch = pocketToGit(pocket)
146 current_version = getVersion(package, branch)
147 if new_version <= current_version:
148 raise InvalidBuild('New version %s of %s is not newer than '
149 'version %s currently in pocket %s' %
150 (new_version, package, current_version, pocket))
152 # Almost by definition, A is a fast-forward of B if B..A is
154 if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)],
156 raise InvalidBuild('New commit %s of %s is not a fast-forward of'
157 'commit currently in pocket %s' %
158 (commit, package, pocket))