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'
22 _DEFAULT_DISTRIBUTION = 'hardy'
25 class InvalidBuild(ValueError):
30 'lenny': '~debian5.0',
31 'squeeze': '~debian6.0',
33 'hardy': '~ubuntu8.04',
34 'lucid': '~ubuntu10.04',
35 'maverick': '~ubuntu10.10',
36 'natty': '~ubuntu11.04',
37 'oneiric': '~ubuntu11.10',
38 'precise': '~ubuntu12.04',
41 def distroToSuffix(distro):
42 return _DISTRO_TO_SUFFIX.get(distro, '~'+distro)
45 """Return the path to the git repo for a given package."""
46 return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package)
48 def ensureValidPackage(package):
49 """Perform some basic sanity checks that the requested repo is in a
50 subdirectory of _REPO_DIR/invirt/packages. This prevents weirdness
51 such as submitting a package like '../prod/...git'. Also ensures that
53 # TODO: this might be easier just to regex
54 repo = os.path.abspath(getRepo(package))
55 parent_dir = os.path.dirname(repo)
56 prefix = os.path.join(_REPO_DIR, 'invirt/packages')
57 if not parent_dir.startswith(prefix):
58 raise InvalidBuild('Invalid package name %s' % package)
59 elif not os.path.exists(repo):
60 raise InvalidBuild('Nonexisting package %s' % package)
62 def canonicalize_commit(package, commit, shorten=False):
67 return c.captureOutput(['git', 'rev-parse'] + flags + [commit],
68 cwd=getRepo(package)).strip()
70 def pocketToGit(pocket):
71 """Map a pocket in the configuration to a git branch."""
72 return getattr(getattr(config.build.pockets, pocket), 'git', pocket)
75 def pocketToApt(pocket):
76 """Map a pocket in the configuration to an apt repo pocket."""
77 return getattr(getattr(config.build.pockets, pocket), 'apt', pocket)
79 def pocketToDistro(pocket):
80 """Map a pocket in the configuration to the distro we build for."""
81 return getattr(getattr(config.build.pockets, pocket), 'distro', _DEFAULT_DISTRIBUTION)
83 def getGitFile(package, ref, path):
84 """Return the contents of a path from a git ref in a package."""
85 return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)],
89 def getChangelog(package, ref):
90 """Get a changelog object for a given ref in a given package.
92 This returns a debian_bundle.changelog.Changelog object for a
93 given ref of a given package.
95 return changelog.Changelog(getGitFile(package, ref, 'debian/changelog'))
97 def runHook(hook, args=[], stdin_str=None):
98 """Run a named hook."""
99 hook = os.path.join(_HOOKS_DIR, hook)
101 c.captureOutput([hook] + args, stdin_str=stdin_str)
105 def getVersion(package, ref):
106 """Get the version of a given package at a particular ref."""
107 return getChangelog(package, ref).get_version()
110 def validateBuild(pocket, package, commit):
111 """Given the parameters of a new build, validate that build.
113 The checks this function performs vary based on whether or not the
114 pocket is configured with allow_backtracking.
116 A build of a pocket without allow_backtracking set must be a
117 fast-forward of the previous revision, and the most recent version
118 in the changelog most be strictly greater than the version
119 currently in the repository.
121 In all cases, this revision of the package can only have the same
122 version number as any other revision currently in the apt
123 repository if they have the same commit ID.
125 If it's unspecified, it is assumed that pocket do not
128 If this build request fails validation, this function will raise a
129 InvalidBuild exception, with information about why the validation
132 If this build request can be satisfied by copying the package from
133 another pocket, then this function returns that pocket. Otherwise,
136 ensureValidPackage(package)
137 package_repo = getRepo(package)
138 new_version = getVersion(package, commit)
139 new_distro = pocketToDistro(pocket)
143 for p in config.build.pockets:
149 current_commit = c.captureOutput(['git', 'rev-parse', b],
150 cwd=package_repo).strip()
151 except subprocess.CalledProcessError:
152 # Guess we haven't created this pocket yet
155 current_version = getVersion(package, b)
156 current_distro = pocketToDistro(pocket)
158 # NB: Neither current_version nor new_version will have the
159 # distro-specific prefix.
161 if current_version == new_version and current_distro == new_distro:
162 if current_commit == commit:
165 raise InvalidBuild('Version %s of %s already available is in '
166 'pocket %s from commit %s' %
167 (new_version, package, p, current_commit))
169 if not config.build.pockets[pocket].get('allow_backtracking', False):
170 branch = pocketToGit(pocket)
171 current_version = getVersion(package, branch)
172 if new_version <= current_version:
173 raise InvalidBuild('New version %s of %s is not newer than '
174 'version %s currently in pocket %s' %
175 (new_version, package, current_version, pocket))
177 # Almost by definition, A is a fast-forward of B if B..A is
179 if c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)],
181 raise InvalidBuild('New commit %s of %s is not a fast-forward of'
182 'commit currently in pocket %s' %
183 (commit, package, pocket))