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.""" |
---|
29 | return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package) |
---|
30 | |
---|
31 | |
---|
32 | def pocketToGit(pocket): |
---|
33 | """Map a pocket in the configuration to a git branch.""" |
---|
34 | return getattr(getattr(config.build.pockets, pocket), 'git', pocket) |
---|
35 | |
---|
36 | |
---|
37 | def pocketToApt(pocket): |
---|
38 | """Map a pocket in the configuration to an apt repo pocket.""" |
---|
39 | return getattr(getattr(config.build.pockets, pocket), '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 | ret = True |
---|
92 | |
---|
93 | for p in config.build.pockets: |
---|
94 | if p == pocket: |
---|
95 | continue |
---|
96 | |
---|
97 | b = pocketToGit(p) |
---|
98 | current_commit = c.captureOutput(['git', 'rev-parse', b], |
---|
99 | cwd=package_repo) |
---|
100 | current_version = getVersion(package, b) |
---|
101 | |
---|
102 | if current_version == new_version: |
---|
103 | if current_commit == commit: |
---|
104 | ret = p |
---|
105 | else: |
---|
106 | raise InvalidBuild('Version %s of %s already available in ' |
---|
107 | 'pocket %s from commit %s' % |
---|
108 | (new_version, package, p, current_commit)) |
---|
109 | |
---|
110 | if config.build.pockets[pocket].get('allow_backtracking', False): |
---|
111 | branch = pocketToGit(pocket) |
---|
112 | current_version = getVersion(package, branch) |
---|
113 | if new_version <= current_version: |
---|
114 | raise InvalidBuild('New version %s of %s is not newer than ' |
---|
115 | 'version %s currently in pocket %s' % |
---|
116 | (new_version, package, current_version, pocket)) |
---|
117 | |
---|
118 | # Almost by definition, A is a fast-forward of B if B..A is |
---|
119 | # empty |
---|
120 | if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)]): |
---|
121 | raise InvalidBuild('New commit %s of %s is not a fast-forward of' |
---|
122 | 'commit currently in pocket %s' % |
---|
123 | (commit, package, pocket)) |
---|
124 | |
---|
125 | return ret |
---|