Merge lp:~cjwatson/launchpad-buildd/recipe-currentlybuilding-permissions into lp:launchpad-buildd

Proposed by Colin Watson
Status: Merged
Merged at revision: 389
Proposed branch: lp:~cjwatson/launchpad-buildd/recipe-currentlybuilding-permissions
Merge into: lp:launchpad-buildd
Diff against target: 227 lines (+133/-12)
3 files modified
bin/buildrecipe (+31/-8)
debian/changelog (+7/-0)
lpbuildd/tests/test_buildrecipe.py (+95/-4)
To merge this branch: bzr merge lp:~cjwatson/launchpad-buildd/recipe-currentlybuilding-permissions
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+371672@code.launchpad.net

Commit message

Fix recipe building to not rely on /CurrentlyBuilding existing in base images.

Description of the change

This involves a bit of copy-and-paste from Chroot.copy_in, since buildrecipe doesn't use the Operation framework yet.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/buildrecipe'
2--- bin/buildrecipe 2017-07-25 22:11:19 +0000
3+++ bin/buildrecipe 2019-08-22 16:24:05 +0000
4@@ -1,5 +1,5 @@
5 #!/usr/bin/python -u
6-# Copyright 2010, 2011 Canonical Ltd. This software is licensed under the
7+# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """A script that builds a package from a recipe and a chroot."""
11@@ -12,6 +12,7 @@
12 import os
13 import pwd
14 import socket
15+import stat
16 from subprocess import (
17 PIPE,
18 Popen,
19@@ -20,6 +21,7 @@
20 check_output,
21 )
22 import sys
23+import tempfile
24 from textwrap import dedent
25
26 from debian.deb822 import Deb822
27@@ -260,8 +262,6 @@
28 def installBuildDeps(self):
29 """Install the build-depends of the source tree."""
30 package = self.getPackageName()
31- currently_building_path = os.path.join(
32- self.chroot_path, 'CurrentlyBuilding')
33 currently_building_contents = (
34 'Package: %s\n'
35 'Suite: %s\n'
36@@ -269,9 +269,11 @@
37 'Purpose: %s\n'
38 'Build-Debug-Symbols: no\n' %
39 (package, self.suite, self.component, self.archive_purpose))
40- currently_building = open(currently_building_path, 'w')
41- currently_building.write(currently_building_contents)
42- currently_building.close()
43+ with tempfile.NamedTemporaryFile(mode='w+') as currently_building:
44+ currently_building.write(currently_building_contents)
45+ currently_building.flush()
46+ os.fchmod(currently_building.fileno(), 0o644)
47+ self.copy_in(currently_building.name, '/CurrentlyBuilding')
48 self.setUpAptArchive(package)
49 return self.chroot(
50 ['apt-get', 'build-dep', '-y', '--only-source', package])
51@@ -286,8 +288,29 @@
52 print("Running in chroot: %s" %
53 ' '.join("'%s'" % arg for arg in args))
54 sys.stdout.flush()
55- return call([
56- '/usr/bin/sudo', '/usr/sbin/chroot', self.chroot_path] + args)
57+ return call(['sudo', '/usr/sbin/chroot', self.chroot_path] + args)
58+
59+ def copy_in(self, source_path, target_path):
60+ """Copy a file into the target environment.
61+
62+ The target file will be owned by root/root and have the same
63+ permission mode as the source file.
64+
65+ :param source_path: the path to the file that should be copied from
66+ the host system.
67+ :param target_path: the path where the file should be installed
68+ inside the target environment, relative to the target
69+ environment's root.
70+ """
71+ # Use install(1) so that we can end up with root/root ownership with
72+ # a minimum of subprocess calls; the buildd user may not make sense
73+ # in the target.
74+ mode = stat.S_IMODE(os.stat(source_path).st_mode)
75+ full_target_path = os.path.join(
76+ self.chroot_path, target_path.lstrip("/"))
77+ check_call(
78+ ["sudo", "install", "-o", "root", "-g", "root", "-m", "%o" % mode,
79+ source_path, full_target_path])
80
81 def buildSourcePackage(self):
82 """Build the source package.
83
84=== modified file 'debian/changelog'
85--- debian/changelog 2019-06-18 16:54:33 +0000
86+++ debian/changelog 2019-08-22 16:24:05 +0000
87@@ -1,3 +1,10 @@
88+launchpad-buildd (177) UNRELEASED; urgency=medium
89+
90+ * Fix recipe building to not rely on /CurrentlyBuilding existing in base
91+ images (LP: #1841075).
92+
93+ -- Colin Watson <cjwatson@ubuntu.com> Thu, 22 Aug 2019 17:16:24 +0100
94+
95 launchpad-buildd (176) xenial; urgency=medium
96
97 * Don't rely on /CurrentlyBuilding existing in base images.
98
99=== modified file 'lpbuildd/tests/test_buildrecipe.py'
100--- lpbuildd/tests/test_buildrecipe.py 2017-10-27 07:42:35 +0000
101+++ lpbuildd/tests/test_buildrecipe.py 2019-08-22 16:24:05 +0000
102@@ -1,4 +1,4 @@
103-# Copyright 2014 Canonical Ltd. This software is licensed under the
104+# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
105 # GNU Affero General Public License version 3 (see the file LICENSE).
106
107 from __future__ import print_function
108@@ -9,11 +9,20 @@
109 import imp
110 import os
111 import shutil
112+import stat
113 import sys
114 import tempfile
115 from textwrap import dedent
116
117+from fixtures import MockPatchObject
118+import six
119+from systemfixtures import FakeProcesses
120 from testtools import TestCase
121+from testtools.matchers import (
122+ Equals,
123+ MatchesListwise,
124+ StartsWith,
125+ )
126
127
128 @contextmanager
129@@ -30,6 +39,23 @@
130 "buildrecipe", "bin/buildrecipe").RecipeBuilder
131
132
133+class RanCommand(MatchesListwise):
134+
135+ def __init__(self, *args):
136+ args_matchers = [
137+ Equals(arg) if isinstance(arg, six.string_types) else arg
138+ for arg in args]
139+ super(RanCommand, self).__init__(args_matchers)
140+
141+
142+class RanInChroot(RanCommand):
143+
144+ def __init__(self, home_dir, *args):
145+ super(RanInChroot, self).__init__(
146+ "sudo", "/usr/sbin/chroot",
147+ os.path.join(home_dir, "build-1", "chroot-autobuild"), *args)
148+
149+
150 class TestRecipeBuilder(TestCase):
151 def setUp(self):
152 super(TestRecipeBuilder, self).setUp()
153@@ -143,6 +169,71 @@
154 self.assertIn(
155 "Build-Depends: debhelper (>= 9~), libfoo-dev\n", sources_text)
156
157- # XXX cjwatson 2015-06-15: We should unit-test enableAptArchive and
158- # installBuildDeps too, but it involves a lot of mocks. For now,
159- # integration testing is probably more useful.
160+ def test_installBuildDeps(self):
161+ processes_fixture = self.useFixture(FakeProcesses())
162+ processes_fixture.add(lambda _: {}, name="sudo")
163+ copies = {}
164+
165+ def mock_copy_in(source_path, target_path):
166+ with open(source_path, "rb") as source:
167+ copies[target_path] = (
168+ source.read(), os.fstat(source.fileno()).st_mode)
169+
170+ self.useFixture(
171+ MockPatchObject(self.builder, "copy_in", mock_copy_in))
172+ self.builder.source_dir_relative = os.path.join(
173+ self.builder.work_dir_relative, "tree", "foo")
174+ changelog_path = os.path.join(
175+ self.builder.work_dir, "tree", "foo", "debian", "changelog")
176+ control_path = os.path.join(
177+ self.builder.work_dir, "tree", "foo", "debian", "control")
178+ os.makedirs(os.path.dirname(changelog_path))
179+ with open(changelog_path, "w") as changelog:
180+ # Not a valid changelog, but only the first line matters here.
181+ print("foo (1.0-1) bionic; urgency=medium", file=changelog)
182+ with open(control_path, "w") as control:
183+ print(dedent("""\
184+ Source: foo
185+ Build-Depends: debhelper (>= 9~), libfoo-dev
186+
187+ Package: foo
188+ Depends: ${shlibs:Depends}"""),
189+ file=control)
190+ self.assertEqual(0, self.builder.installBuildDeps())
191+ self.assertThat(
192+ [proc._args["args"] for proc in processes_fixture.procs],
193+ MatchesListwise([
194+ RanInChroot(
195+ self.home_dir, "apt-get",
196+ "-o", StartsWith("Dir::Etc::sourcelist="),
197+ "-o", "APT::Get::List-Cleanup=false",
198+ "update"),
199+ RanCommand(
200+ "sudo", "mv",
201+ os.path.join(
202+ self.builder.apt_dir, "buildrecipe-archive.list"),
203+ os.path.join(
204+ self.builder.apt_sources_list_dir,
205+ "buildrecipe-archive.list")),
206+ RanInChroot(
207+ self.home_dir, "apt-get",
208+ "build-dep", "-y", "--only-source", "foo"),
209+ ]))
210+ self.assertEqual(
211+ (dedent("""\
212+ Package: foo
213+ Suite: grumpy
214+ Component: main
215+ Purpose: PPA
216+ Build-Debug-Symbols: no
217+ """).encode("UTF-8"), stat.S_IFREG | 0o644),
218+ copies["/CurrentlyBuilding"])
219+ # This is still in the temporary location, since we mocked the "sudo
220+ # mv" command.
221+ with open(os.path.join(
222+ self.builder.apt_dir,
223+ "buildrecipe-archive.list")) as tmp_list:
224+ self.assertEqual(
225+ "deb-src [trusted=yes] file://%s ./\n" %
226+ self.builder.apt_dir_relative,
227+ tmp_list.read())

Subscribers

People subscribed via source and target branches

to all changes: