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()
109 def pocketExists(pocket, repo):
110 branch = pocketToGit(pocket)
112 c.captureOutput(['git', 'rev-parse', branch], cwd=repo)
113 except subprocess.CalledProcessError:
117 def validateBuild(pocket, package, commit):
118 """Given the parameters of a new build, validate that build.
120 The checks this function performs vary based on whether or not the
121 pocket is configured with allow_backtracking.
123 A build of a pocket without allow_backtracking set must be a
124 fast-forward of the previous revision, and the most recent version
125 in the changelog most be strictly greater than the version
126 currently in the repository.
128 In all cases, this revision of the package can only have the same
129 version number as any other revision currently in the apt
130 repository if they have the same commit ID.
132 If it's unspecified, it is assumed that pocket do not
135 If this build request fails validation, this function will raise a
136 InvalidBuild exception, with information about why the validation
139 If this build request can be satisfied by copying the package from
140 another pocket, then this function returns that pocket. Otherwise,
143 ensureValidPackage(package)
144 package_repo = getRepo(package)
145 new_version = getVersion(package, commit)
146 new_distro = pocketToDistro(pocket)
150 for p in config.build.pockets:
156 current_commit = c.captureOutput(['git', 'rev-parse', b],
157 cwd=package_repo).strip()
158 except subprocess.CalledProcessError:
159 # Guess we haven't created this pocket yet
162 current_version = getVersion(package, b)
163 current_distro = pocketToDistro(p)
165 # NB: Neither current_version nor new_version will have the
166 # distro-specific prefix.
168 if current_version == new_version and current_distro == new_distro:
169 if current_commit == commit:
172 raise InvalidBuild('Version %s of %s already available is in '
173 'pocket %s from commit %s' %
174 (new_version, package, p, current_commit))
176 if not config.build.pockets[pocket].get('allow_backtracking', False):
177 if not pocketExists(pocket, package_repo):
180 branch = pocketToGit(pocket)
181 current_version = getVersion(package, branch)
182 if new_version <= current_version:
183 raise InvalidBuild('New version %s of %s is not newer than '
184 'version %s currently in pocket %s' %
185 (new_version, package, current_version, pocket))
187 # Almost by definition, A is a fast-forward of B if B..A is
189 if c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)],
191 raise InvalidBuild('New commit %s of %s is not a fast-forward of'
192 'commit currently in pocket %s' %
193 (commit, package, pocket))