Merge ~cjwatson/launchpad-buildd:lpcraft-common-output-directory into launchpad-buildd:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: d380315758265bb244bae24f6e034ac953c92415
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad-buildd:lpcraft-common-output-directory
Merge into: launchpad-buildd:master
Diff against target: 265 lines (+57/-37)
5 files modified
debian/changelog (+6/-0)
lpbuildd/ci.py (+13/-9)
lpbuildd/target/run_ci.py (+6/-3)
lpbuildd/target/tests/test_run_ci.py (+19/-19)
lpbuildd/tests/test_ci.py (+13/-6)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+427734@code.launchpad.net

Commit message

Use a common output directory for all lpcraft jobs

Description of the change

In order to implement the `input` keyword in lpcraft, launchpad-buildd needs to use the same top-level output directory for each job so that lpcraft can find artifacts from previously-executed jobs. This is problematic with the current arrangements, because launchpad-buildd has to pass different `--output-directory` options for each individual job so that it can accurately determine which output artifacts belong to which job.

However, with https://code.launchpad.net/~cjwatson/lpcraft/+git/lpcraft/+merge/427724, it becomes possible for launchpad-buildd to identify artifacts by job name and index within a single common output directory, so we can take advantage of that by looking for output files more precisely.

This also gathers the `properties` file (currently unused) in a slightly different way, preparing for Launchpad to extract those output properties and store them in the database in a way that can conveniently be used downstream.

This mustn't be merged until https://code.launchpad.net/~cjwatson/lpcraft/+git/lpcraft/+merge/427724 has been released to the `stable` channel.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 810b64d..679201c 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,9 @@
6+launchpad-buildd (218) UNRELEASED; urgency=medium
7+
8+ * Use a common output directory for all lpcraft jobs.
9+
10+ -- Colin Watson <cjwatson@ubuntu.com> Tue, 02 Aug 2022 15:55:19 +0100
11+
12 launchpad-buildd (217) focal; urgency=medium
13
14 [ Colin Watson ]
15diff --git a/lpbuildd/ci.py b/lpbuildd/ci.py
16index ac6c585..fcfd8d4 100644
17--- a/lpbuildd/ci.py
18+++ b/lpbuildd/ci.py
19@@ -241,16 +241,20 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
20 This is called once for each CI job in the pipeline.
21 """
22 job_status = {}
23- output_path = os.path.join("/build", "output", self.current_job_id)
24- log_path = "%s.log" % output_path
25- if self.backend.path_exists(log_path):
26- log_name = "%s.log" % self.current_job_id
27- self.addWaitingFileFromBackend(log_path, log_name)
28- job_status["log"] = self._builder.waitingfiles[log_name]
29- if self.backend.path_exists(output_path):
30+ job_name, job_index = self.current_job
31+ job_output_path = os.path.join(
32+ "/build", "output", job_name, str(job_index))
33+ for item_name in ("log", "properties"):
34+ item_path = os.path.join(job_output_path, item_name)
35+ if self.backend.path_exists(item_path):
36+ item_id = f"{self.current_job_id}.{item_name}"
37+ self.addWaitingFileFromBackend(item_path, name=item_id)
38+ job_status[item_name] = self._builder.waitingfiles[item_id]
39+ files_path = os.path.join(job_output_path, "files")
40+ if self.backend.path_exists(files_path):
41 for entry in sorted(self.backend.find(
42- output_path, include_directories=False)):
43- path = os.path.join(output_path, entry)
44+ files_path, include_directories=False)):
45+ path = os.path.join(files_path, entry)
46 if self.backend.islink(path):
47 continue
48 entry_base = os.path.basename(entry)
49diff --git a/lpbuildd/target/run_ci.py b/lpbuildd/target/run_ci.py
50index 75deaf9..7a8fa3b 100644
51--- a/lpbuildd/target/run_ci.py
52+++ b/lpbuildd/target/run_ci.py
53@@ -127,8 +127,11 @@ class RunCI(BuilderProxyOperationMixin, Operation):
54 env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
55 job_id = f"{self.args.job_name}:{self.args.job_index}"
56 logger.info("Running %s" % job_id)
57- output_path = os.path.join("/build", "output", job_id)
58- self.backend.run(["mkdir", "-p", output_path])
59+ output_path = os.path.join("/build", "output")
60+ # This matches the per-job output path used by lpcraft.
61+ job_output_path = os.path.join(
62+ output_path, self.args.job_name, str(self.args.job_index))
63+ self.backend.run(["mkdir", "-p", job_output_path])
64 lpcraft_args = [
65 "lpcraft",
66 "-v",
67@@ -161,7 +164,7 @@ class RunCI(BuilderProxyOperationMixin, Operation):
68
69 escaped_lpcraft_args = (
70 " ".join(shell_escape(arg) for arg in lpcraft_args))
71- tee_args = ["tee", "%s.log" % output_path]
72+ tee_args = ["tee", os.path.join(job_output_path, "log")]
73 escaped_tee_args = " ".join(shell_escape(arg) for arg in tee_args)
74 args = [
75 "/bin/bash", "-o", "pipefail", "-c",
76diff --git a/lpbuildd/target/tests/test_run_ci.py b/lpbuildd/target/tests/test_run_ci.py
77index 2f18bd5..ed6e0a2 100644
78--- a/lpbuildd/target/tests/test_run_ci.py
79+++ b/lpbuildd/target/tests/test_run_ci.py
80@@ -316,11 +316,11 @@ class TestRunCI(TestCase):
81 run_ci = parse_args(args=args).operation
82 run_ci.run_job()
83 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
84- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
85+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
86 RanBuildCommand([
87 "/bin/bash", "-o", "pipefail", "-c",
88- "lpcraft -v run-one --output-directory /build/output/test:0 test 0 2>&1 " # noqa: E501
89- "| tee /build/output/test:0.log",
90+ "lpcraft -v run-one --output-directory /build/output test 0 2>&1 " # noqa: E501
91+ "| tee /build/output/test/0/log",
92 ], cwd="/build/tree"),
93 ]))
94
95@@ -340,11 +340,11 @@ class TestRunCI(TestCase):
96 "SNAPPY_STORE_NO_CDN": "1",
97 }
98 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
99- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
100+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
101 RanBuildCommand([
102 "/bin/bash", "-o", "pipefail", "-c",
103- "lpcraft -v run-one --output-directory /build/output/test:0 test 0 2>&1 " # noqa: E501
104- "| tee /build/output/test:0.log",
105+ "lpcraft -v run-one --output-directory /build/output test 0 2>&1 " # noqa: E501
106+ "| tee /build/output/test/0/log",
107 ], cwd="/build/tree", **env),
108 ]))
109
110@@ -359,15 +359,15 @@ class TestRunCI(TestCase):
111 run_ci = parse_args(args=args).operation
112 run_ci.run_job()
113 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
114- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
115+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
116 RanBuildCommand([
117 "/bin/bash", "-o", "pipefail", "-c",
118- "lpcraft -v run-one --output-directory /build/output/test:0 "
119+ "lpcraft -v run-one --output-directory /build/output "
120 "test 0 "
121 "--set-env PIP_INDEX_URL=http://example "
122 "--set-env SOME_PATH=/etc/some_path "
123 "2>&1 "
124- "| tee /build/output/test:0.log",
125+ "| tee /build/output/test/0/log",
126 ], cwd="/build/tree"),
127 ]))
128
129@@ -384,15 +384,15 @@ class TestRunCI(TestCase):
130 run_ci = parse_args(args=args).operation
131 run_ci.run_job()
132 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
133- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
134+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
135 RanBuildCommand([
136 "/bin/bash", "-o", "pipefail", "-c",
137- "lpcraft -v run-one --output-directory /build/output/test:0 "
138+ "lpcraft -v run-one --output-directory /build/output "
139 "test 0 "
140 "--apt-replace-repositories 'deb http://archive.ubuntu.com/ubuntu/ focal main restricted' " # noqa: E501
141 "--apt-replace-repositories 'deb http://archive.ubuntu.com/ubuntu/ focal universe' " # noqa: E501
142 "2>&1 "
143- "| tee /build/output/test:0.log",
144+ "| tee /build/output/test/0/log",
145 ], cwd="/build/tree"),
146 ]))
147
148@@ -407,15 +407,15 @@ class TestRunCI(TestCase):
149 run_ci = parse_args(args=args).operation
150 run_ci.run_job()
151 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
152- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
153+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
154 RanBuildCommand([
155 "/bin/bash", "-o", "pipefail", "-c",
156- "lpcraft -v run-one --output-directory /build/output/test:0 "
157+ "lpcraft -v run-one --output-directory /build/output "
158 "test 0 "
159 "--plugin-setting "
160 "miniconda_conda_channel=https://user:pass@canonical.example.com/artifactory/soss-conda-stable-local/ " # noqa: E501
161 "2>&1 "
162- "| tee /build/output/test:0.log",
163+ "| tee /build/output/test/0/log",
164 ], cwd="/build/tree"),
165 ]))
166
167@@ -429,14 +429,14 @@ class TestRunCI(TestCase):
168 run_ci = parse_args(args=args).operation
169 run_ci.run_job()
170 self.assertThat(run_ci.backend.run.calls, MatchesListwise([
171- RanCommand(["mkdir", "-p", "/build/output/test:0"]),
172+ RanCommand(["mkdir", "-p", "/build/output/test/0"]),
173 RanBuildCommand([
174 "/bin/bash", "-o", "pipefail", "-c",
175- "lpcraft -v run-one --output-directory /build/output/test:0 "
176+ "lpcraft -v run-one --output-directory /build/output "
177 "test 0 "
178 "--secrets /build/.launchpad-secrets.yaml "
179 "2>&1 "
180- "| tee /build/output/test:0.log",
181+ "| tee /build/output/test/0/log",
182 ], cwd="/build/tree"),
183 ]))
184
185@@ -451,7 +451,7 @@ class TestRunCI(TestCase):
186 # Just check that it did something in each step, not every detail.
187 self.assertThat(
188 run_ci.backend.run.calls,
189- AnyMatch(RanCommand(["mkdir", "-p", "/build/output/test:0"])))
190+ AnyMatch(RanCommand(["mkdir", "-p", "/build/output/test/0"])))
191
192 def test_run_install_fails(self):
193 class FailInstall(FakeMethod):
194diff --git a/lpbuildd/tests/test_ci.py b/lpbuildd/tests/test_ci.py
195index f67300d..8fa6ee7 100644
196--- a/lpbuildd/tests/test_ci.py
197+++ b/lpbuildd/tests/test_ci.py
198@@ -148,17 +148,19 @@ class TestCIBuildManagerIteration(TestCase):
199 ]
200 yield self.expectRunJob("build", "0", options=expected_job_options)
201 self.buildmanager.backend.add_file(
202- "/build/output/build:0.log", b"I am a CI build job log.")
203+ "/build/output/build/0/log", b"I am a CI build job log.")
204 self.buildmanager.backend.add_file(
205- "/build/output/build:0/ci.whl",
206+ "/build/output/build/0/files/ci.whl",
207 b"I am output from a CI build job.")
208+ self.buildmanager.backend.add_file(
209+ "/build/output/build/0/properties", b'{"key": "value"}')
210
211 # Collect the output of the first job and start running the second.
212 yield self.expectRunJob("test", "0", options=expected_job_options)
213 self.buildmanager.backend.add_file(
214- "/build/output/test:0.log", b"I am a CI test job log.")
215+ "/build/output/test/0/log", b"I am a CI test job log.")
216 self.buildmanager.backend.add_file(
217- "/build/output/test:0/ci.tar.gz",
218+ "/build/output/test/0/files/ci.tar.gz",
219 b"I am output from a CI test job.")
220
221 # Output from the first job is visible in the status response.
222@@ -167,6 +169,8 @@ class TestCIBuildManagerIteration(TestCase):
223 {
224 "build:0": {
225 "log": self.builder.waitingfiles["build:0.log"],
226+ "properties": (
227+ self.builder.waitingfiles["build:0.properties"]),
228 "output": {
229 "ci.whl": self.builder.waitingfiles["build:0/ci.whl"],
230 },
231@@ -188,6 +192,7 @@ class TestCIBuildManagerIteration(TestCase):
232 self.assertFalse(self.builder.wasCalled("buildFail"))
233 self.assertThat(self.builder, HasWaitingFiles.byEquality({
234 "build:0.log": b"I am a CI build job log.",
235+ "build:0.properties": b'{"key": "value"}',
236 "build:0/ci.whl": b"I am output from a CI build job.",
237 "test:0.log": b"I am a CI test job log.",
238 "test:0/ci.tar.gz": b"I am output from a CI test job.",
239@@ -199,6 +204,8 @@ class TestCIBuildManagerIteration(TestCase):
240 {
241 "build:0": {
242 "log": self.builder.waitingfiles["build:0.log"],
243+ "properties": (
244+ self.builder.waitingfiles["build:0.properties"]),
245 "output": {
246 "ci.whl": self.builder.waitingfiles["build:0/ci.whl"],
247 },
248@@ -283,7 +290,7 @@ class TestCIBuildManagerIteration(TestCase):
249 ]
250 yield self.expectRunJob("lint", "0", options=expected_job_options)
251 self.buildmanager.backend.add_file(
252- "/build/output/lint:0.log", b"I am a failing CI lint job log.")
253+ "/build/output/lint/0/log", b"I am a failing CI lint job log.")
254
255 # Collect the output of the first job and start running the second.
256 # (Note that `retcode` is the return code of the *first* job, not the
257@@ -292,7 +299,7 @@ class TestCIBuildManagerIteration(TestCase):
258 "build", "0", options=expected_job_options,
259 retcode=RETCODE_FAILURE_BUILD)
260 self.buildmanager.backend.add_file(
261- "/build/output/build:0.log", b"I am a CI build job log.")
262+ "/build/output/build/0/log", b"I am a CI build job log.")
263
264 # Output from the first job is visible in the status response.
265 extra_status = self.buildmanager.status()

Subscribers

People subscribed via source and target branches