c1730dfac0fd84d1403328319bea01fa97e10e06
[invirt/packages/invirt-dev.git] / python / invirt / builder.py
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