Merge ~pappacena/launchpad:oci-build-info-to-buildd into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 66ce7db6ae6880b85897835f1ec9bec78a8d09dd
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:oci-build-info-to-buildd
Merge into: launchpad:master
Diff against target: 244 lines (+151/-4)
3 files modified
database/schema/security.cfg (+1/-0)
lib/lp/oci/model/ocirecipebuildbehaviour.py (+36/-0)
lib/lp/oci/tests/test_ocirecipebuildbehaviour.py (+114/-4)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+392500@code.launchpad.net

Commit message

Adding extra meta info when requesting an OCI build to buildd

Description of the change

This information will be useful on buildd side to create the OCI security manifest file.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Mostly looks OK, thanks. The processor name vs. architecture tag issue definitely needs fixing; my other comments are nice-to-have.

review: Approve
66ce7db... by Thiago F. Pappacena

Improving tests and small refactoring

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Pushed the requested changes. I'll land this.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/database/schema/security.cfg b/database/schema/security.cfg
2index d5a23f1..11aba6f 100644
3--- a/database/schema/security.cfg
4+++ b/database/schema/security.cfg
5@@ -1004,6 +1004,7 @@ public.ocipushrule = SELECT
6 public.ocirecipe = SELECT
7 public.ocirecipebuild = SELECT, UPDATE
8 public.ocirecipebuildjob = SELECT, INSERT
9+public.ocirecipejob = SELECT
10 public.openididentifier = SELECT
11 public.packageset = SELECT
12 public.packagesetgroup = SELECT
13diff --git a/lib/lp/oci/model/ocirecipebuildbehaviour.py b/lib/lp/oci/model/ocirecipebuildbehaviour.py
14index 1008884..7a8d7fb 100644
15--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
16+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
17@@ -38,6 +38,7 @@ from lp.buildmaster.model.buildfarmjobbehaviour import (
18 from lp.oci.interfaces.ocirecipebuild import IOCIFileSet
19 from lp.registry.interfaces.series import SeriesStatus
20 from lp.services.librarian.utils import copy_and_close
21+from lp.services.webapp import canonical_url
22 from lp.snappy.model.snapbuildbehaviour import SnapProxyMixin
23 from lp.soyuz.adapters.archivedependencies import (
24 get_sources_list_for_building,
25@@ -77,6 +78,40 @@ class OCIRecipeBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
26 raise CannotBuild(
27 "Missing chroot for %s" % build.distro_arch_series.displayname)
28
29+ def _getBuildInfoArgs(self):
30+ def format_user(user):
31+ if user is None:
32+ return None
33+ hide_email = not user.preferredemail or user.hide_email_addresses
34+ return {
35+ "name": user.name,
36+ "email": (None if hide_email else user.preferredemail.email)}
37+ build = self.build
38+ build_request = build.build_request
39+ builds = list(build_request.builds) if build_request else [build]
40+ info = {
41+ "architectures": [],
42+ "recipe_owner": format_user(self.build.recipe.owner),
43+ "build_request_id": None,
44+ "build_request_timestamp": None,
45+ # With build_request set, all builds in this list will have the
46+ # same requester. Without build_request, we only care about the
47+ # only existing build in this list.
48+ "build_requester": format_user(builds[0].requester),
49+ # Build URL per architecture.
50+ "build_urls": {},
51+ }
52+ if build_request:
53+ info["build_request_id"] = build_request.id
54+ info["build_request_timestamp"] = (
55+ build_request.date_requested.isoformat())
56+ info["architectures"] = [i.distro_arch_series.architecturetag
57+ for i in builds]
58+ info["build_urls"] = {
59+ i.distro_arch_series.architecturetag: canonical_url(i)
60+ for i in builds}
61+ return info
62+
63 @defer.inlineCallbacks
64 def extraBuildArgs(self, logger=None):
65 """
66@@ -101,6 +136,7 @@ class OCIRecipeBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
67 # XML-RPC.
68 args['build_args'] = removeSecurityProxy(build.recipe.build_args)
69 args['build_path'] = build.recipe.build_path
70+ args['metadata'] = self._getBuildInfoArgs()
71
72 if build.recipe.git_ref is not None:
73 args["git_repository"] = (
74diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
75index ded60cf..562cb26 100644
76--- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
77+++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
78@@ -29,6 +29,7 @@ from testtools.matchers import (
79 IsInstance,
80 MatchesDict,
81 MatchesListwise,
82+ MatchesSetwise,
83 StartsWith,
84 )
85 from testtools.twistedsupport import (
86@@ -104,7 +105,7 @@ class MakeOCIBuildMixin:
87 build.queueBuild()
88 return build
89
90- def makeJob(self, git_ref, recipe=None, build=None, **kwargs):
91+ def makeJob(self, git_ref=None, recipe=None, build=None, **kwargs):
92 """Create a sample `IOCIRecipeBuildBehaviour`."""
93 if build is None:
94 if recipe is None:
95@@ -112,6 +113,8 @@ class MakeOCIBuildMixin:
96 else:
97 build = self.factory.makeOCIRecipeBuild(
98 recipe=recipe, **kwargs)
99+ if git_ref is None:
100+ [git_ref] = self.factory.makeGitRefs()
101 build.recipe.git_ref = git_ref
102 build.recipe.build_args = {"BUILD_VAR": "123"}
103
104@@ -131,7 +134,7 @@ class MakeOCIBuildMixin:
105 return job
106
107
108-class TestOCIBuildBehaviour(TestCaseWithFactory):
109+class TestOCIBuildBehaviour(TestCaseWithFactory, MakeOCIBuildMixin):
110
111 layer = LaunchpadZopelessLayer
112
113@@ -150,6 +153,99 @@ class TestOCIBuildBehaviour(TestCaseWithFactory):
114 job = IBuildFarmJobBehaviour(build)
115 self.assertProvides(job, IBuildFarmJobBehaviour)
116
117+ def makeRecipe(self, processor_names, **kwargs):
118+ recipe = self.factory.makeOCIRecipe(**kwargs)
119+ processors_list = []
120+ distroseries = self.factory.makeDistroSeries(
121+ distribution=recipe.oci_project.distribution)
122+ for proc_name in processor_names:
123+ proc = getUtility(IProcessorSet).getByName(proc_name)
124+ distro = self.factory.makeDistroArchSeries(
125+ distroseries=distroseries, architecturetag=proc_name,
126+ processor=proc)
127+ distro.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
128+ processors_list.append(proc)
129+ recipe.setProcessors(processors_list)
130+ return recipe
131+
132+ def makeBuildRequest(self, recipe, requester):
133+ build_request = recipe.requestBuilds(requester)
134+ # Create the builds for the build request, and set them at the build
135+ # request job.
136+ builds = recipe.requestBuildsFromJob(requester, build_request)
137+ job = removeSecurityProxy(build_request).job
138+ removeSecurityProxy(job).builds = builds
139+ return build_request
140+
141+ def test_getBuildInfoArgs_with_build_request(self):
142+ owner = self.factory.makePerson()
143+ owner.setPreferredEmail(self.factory.makeEmail('owner@foo.com', owner))
144+ oci_project = self.factory.makeOCIProject(registrant=owner)
145+ recipe = self.makeRecipe(
146+ processor_names=["amd64", "386"],
147+ oci_project=oci_project, registrant=owner, owner=owner)
148+ build_request = self.makeBuildRequest(recipe, recipe.owner)
149+ self.assertEqual(2, build_request.builds.count())
150+ build = build_request.builds[0]
151+ build_per_proc = {i.processor.name: i for i in build_request.builds}
152+ job = self.makeJob(build=build)
153+
154+ self.assertThat(job._getBuildInfoArgs(), MatchesDict({
155+ "architectures": MatchesSetwise(Equals("amd64"), Equals("386")),
156+ "recipe_owner": Equals({
157+ "name": recipe.owner.name,
158+ "email": "owner@foo.com"}),
159+ "build_request_id": Equals(build_request.id),
160+ "build_requester": Equals({
161+ "name": build.requester.name,
162+ "email": "owner@foo.com"}),
163+ "build_request_timestamp": Equals(
164+ build_request.date_requested.isoformat()),
165+ "build_urls": MatchesDict({
166+ "amd64": Equals(canonical_url(build_per_proc["amd64"])),
167+ "386": Equals(canonical_url(build_per_proc["386"]))
168+ }),
169+ }))
170+
171+ def test_getBuildInfoArgs_hide_email(self):
172+ owner = self.factory.makePerson()
173+ owner.setPreferredEmail(self.factory.makeEmail('owner@foo.com', owner))
174+ owner.hide_email_addresses = True
175+ oci_project = self.factory.makeOCIProject(registrant=owner)
176+ recipe = self.makeRecipe(
177+ processor_names=["amd64"],
178+ oci_project=oci_project, registrant=owner, owner=owner)
179+ build_request = self.makeBuildRequest(recipe, recipe.owner)
180+ build = build_request.builds[0]
181+ job = self.makeJob(build=build)
182+
183+ self.assertThat(job._getBuildInfoArgs(), MatchesDict({
184+ "architectures": Equals(["amd64"]),
185+ "recipe_owner": Equals({"name": recipe.owner.name, "email": None}),
186+ "build_request_id": Equals(build_request.id),
187+ "build_requester": Equals({
188+ "name": build.requester.name, "email": None}),
189+ "build_request_timestamp": Equals(
190+ build_request.date_requested.isoformat()),
191+ "build_urls": MatchesDict({
192+ "amd64": Equals(canonical_url(build_request.builds[0]))
193+ }),
194+ }))
195+
196+ def test_getBuildInfoArgs_without_build_request(self):
197+ recipe = self.makeRecipe(processor_names=["amd64"])
198+ distro_arch_series = removeSecurityProxy(
199+ recipe.getAllowedArchitectures()[0])
200+ build = self.factory.makeOCIRecipeBuild(
201+ recipe=recipe, distro_arch_series=distro_arch_series)
202+ job = self.makeJob(build=build)
203+ self.assertThat(job._getBuildInfoArgs(), ContainsDict({
204+ "architectures": Equals(["amd64"]),
205+ "build_request_id": Equals(None),
206+ "build_request_timestamp": Equals(None),
207+ "build_urls": MatchesDict({"amd64": Equals(canonical_url(build))}),
208+ }))
209+
210
211 class TestAsyncOCIRecipeBuildBehaviour(
212 StatsMixin, MakeOCIBuildMixin, TestCaseWithFactory):
213@@ -279,7 +375,14 @@ class TestAsyncOCIRecipeBuildBehaviour(
214 "revocation_endpoint": RevocationEndpointMatcher(job, self.now),
215 "series": Equals(job.build.distro_arch_series.distroseries.name),
216 "trusted_keys": Equals(expected_trusted_keys),
217- }))
218+ # 'metadata' has detailed tests at TestOCIBuildBehaviour class.
219+ "metadata": ContainsDict({
220+ "architectures": Equals(["i386"]),
221+ "build_request_id": Equals(None),
222+ "build_request_timestamp": Equals(None),
223+ "build_urls": Equals({"i386": canonical_url(job.build)})
224+ })
225+ }))
226
227 @defer.inlineCallbacks
228 def test_extraBuildArgs_git_HEAD(self):
229@@ -311,7 +414,14 @@ class TestAsyncOCIRecipeBuildBehaviour(
230 "revocation_endpoint": RevocationEndpointMatcher(job, self.now),
231 "series": Equals(job.build.distro_arch_series.distroseries.name),
232 "trusted_keys": Equals(expected_trusted_keys),
233- }))
234+ # 'metadata' has detailed tests at TestOCIBuildBehaviour class.
235+ "metadata": ContainsDict({
236+ "architectures": Equals(["i386"]),
237+ "build_request_id": Equals(None),
238+ "build_request_timestamp": Equals(None),
239+ "build_urls": Equals({"i386": canonical_url(job.build)})
240+ })
241+ }))
242
243 @defer.inlineCallbacks
244 def test_composeBuildRequest_proxy_url_set(self):