Merge ~pappacena/launchpad:ui-manage-ocirecipe-for-projects into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 95f67e6773ab6fbec0ae959e8d6022b5d9e8fcd7
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:ui-manage-ocirecipe-for-projects
Merge into: launchpad:master
Prerequisite: ~pappacena/launchpad:ui-create-ociproject-for-projects
Diff against target: 221 lines (+91/-10)
5 files modified
lib/lp/oci/browser/tests/test_ocirecipe.py (+16/-2)
lib/lp/oci/interfaces/ocirecipe.py (+2/-0)
lib/lp/oci/model/ocirecipe.py (+20/-5)
lib/lp/oci/tests/test_ocirecipe.py (+40/-0)
lib/lp/registry/browser/personproduct.py (+13/-3)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+384755@code.launchpad.net

Commit message

Adjustments to make OCIRecipe work also with OCIProjects based on project, rather then distributions.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) :
95f67e6... by Thiago F. Pappacena

Adding feature flag to control distribution for project-based OCI recipes

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

twom, added the feature flag to control which distribution should be used for OCIRecipes of OCIProjects based on IProduct. We fallback gracefully to Ubuntu if the feature flag is not set.

Revision history for this message
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py
2index 6633966..e25d463 100644
3--- a/lib/lp/oci/browser/tests/test_ocirecipe.py
4+++ b/lib/lp/oci/browser/tests/test_ocirecipe.py
5@@ -91,8 +91,22 @@ class TestOCIRecipeNavigation(TestCaseWithFactory):
6 "http://launchpad.test/~person/distro/+oci/oci-project/"
7 "+recipe/recipe", canonical_url(recipe))
8
9- def test_recipe(self):
10- recipe = self.factory.makeOCIRecipe()
11+ def test_recipe_traverse_distribution(self):
12+ # Make sure we can reach recipe of distro-based OCI projects.
13+ distro = self.factory.makeDistribution()
14+ oci_project = self.factory.makeOCIProject(pillar=distro)
15+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
16+ obj, _, _ = test_traverse(
17+ "http://launchpad.test/~%s/%s/+oci/%s/+recipe/%s" % (
18+ recipe.owner.name, recipe.oci_project.pillar.name,
19+ recipe.oci_project.name, recipe.name))
20+ self.assertEqual(recipe, obj)
21+
22+ def test_recipe_traverse_project(self):
23+ # Make sure we can reach recipe of project-based OCI projects.
24+ project = self.factory.makeProduct()
25+ oci_project = self.factory.makeOCIProject(pillar=project)
26+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
27 obj, _, _ = test_traverse(
28 "http://launchpad.test/~%s/%s/+oci/%s/+recipe/%s" % (
29 recipe.owner.name, recipe.oci_project.pillar.name,
30diff --git a/lib/lp/oci/interfaces/ocirecipe.py b/lib/lp/oci/interfaces/ocirecipe.py
31index 4b2b06d..3628d71 100644
32--- a/lib/lp/oci/interfaces/ocirecipe.py
33+++ b/lib/lp/oci/interfaces/ocirecipe.py
34@@ -21,6 +21,7 @@ __all__ = [
35 'OCIRecipeFeatureDisabled',
36 'OCIRecipeNotOwner',
37 'OCI_RECIPE_ALLOW_CREATE',
38+ 'OCI_RECIPE_BUILD_DISTRIBUTION',
39 'OCI_RECIPE_WEBHOOKS_FEATURE_FLAG',
40 ]
41
42@@ -80,6 +81,7 @@ from lp.services.webhooks.interfaces import IWebhookTarget
43
44 OCI_RECIPE_WEBHOOKS_FEATURE_FLAG = "oci.recipe.webhooks.enabled"
45 OCI_RECIPE_ALLOW_CREATE = 'oci.recipe.create.enabled'
46+OCI_RECIPE_BUILD_DISTRIBUTION = 'oci.default_build_distribution'
47
48
49 @error_status(http_client.UNAUTHORIZED)
50diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py
51index 8562778..f5ecbdf 100644
52--- a/lib/lp/oci/model/ocirecipe.py
53+++ b/lib/lp/oci/model/ocirecipe.py
54@@ -36,6 +36,7 @@ from zope.interface import implementer
55 from zope.security.interfaces import Unauthorized
56 from zope.security.proxy import removeSecurityProxy
57
58+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
59 from lp.app.interfaces.security import IAuthorization
60 from lp.buildmaster.enums import BuildStatus
61 from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
62@@ -56,6 +57,7 @@ from lp.oci.interfaces.ocirecipe import (
63 NoSourceForOCIRecipe,
64 NoSuchOCIRecipe,
65 OCI_RECIPE_ALLOW_CREATE,
66+ OCI_RECIPE_BUILD_DISTRIBUTION,
67 OCIRecipeBuildAlreadyPending,
68 OCIRecipeFeatureDisabled,
69 OCIRecipeNotOwner,
70@@ -67,6 +69,7 @@ from lp.oci.interfaces.ociregistrycredentials import (
71 )
72 from lp.oci.model.ocipushrule import OCIPushRule
73 from lp.oci.model.ocirecipebuild import OCIRecipeBuild
74+from lp.registry.interfaces.distribution import IDistributionSet
75 from lp.registry.interfaces.person import IPersonSet
76 from lp.registry.interfaces.role import IPersonRoles
77 from lp.registry.model.distribution import Distribution
78@@ -216,9 +219,20 @@ class OCIRecipe(Storm, WebhookTargetMixin):
79
80 @property
81 def distribution(self):
82- # XXX twom 2019-12-05 This may need to change when an OCIProject
83- # pillar isn't just a distribution
84- return self.oci_project.distribution
85+ if self.oci_project.distribution:
86+ return self.oci_project.distribution
87+ # For OCI projects that are not based on distribution, we use the
88+ # default distribution set by the following feature flag (or
89+ # defaults to Ubuntu, if none is set).
90+ distro_name = getFeatureFlag(OCI_RECIPE_BUILD_DISTRIBUTION)
91+ if not distro_name:
92+ return getUtility(ILaunchpadCelebrities).ubuntu
93+ distro = getUtility(IDistributionSet).getByName(distro_name)
94+ if not distro:
95+ raise ValueError(
96+ "'%s' is not a valid value for feature flag '%s'" %
97+ (distro_name, OCI_RECIPE_BUILD_DISTRIBUTION))
98+ return distro
99
100 @property
101 def distro_series(self):
102@@ -237,9 +251,10 @@ class OCIRecipe(Storm, WebhookTargetMixin):
103 """See `IOCIRecipe`."""
104 clauses = [Processor.id == DistroArchSeries.processor_id]
105 if self.distro_series is not None:
106+ enabled_archs_resultset = removeSecurityProxy(
107+ self.distro_series.enabled_architectures)
108 clauses.append(DistroArchSeries.id.is_in(
109- self.distro_series.enabled_architectures.get_select_expr(
110- DistroArchSeries.id)))
111+ enabled_archs_resultset.get_select_expr(DistroArchSeries.id)))
112 else:
113 # We might not know the series if the OCI project's distribution
114 # has no series at all, which can happen in tests. Fall back to
115diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py
116index 1c69638..d728cf7 100644
117--- a/lib/lp/oci/tests/test_ocirecipe.py
118+++ b/lib/lp/oci/tests/test_ocirecipe.py
119@@ -43,6 +43,7 @@ from lp.oci.interfaces.ocirecipe import (
120 NoSourceForOCIRecipe,
121 NoSuchOCIRecipe,
122 OCI_RECIPE_ALLOW_CREATE,
123+ OCI_RECIPE_BUILD_DISTRIBUTION,
124 OCI_RECIPE_WEBHOOKS_FEATURE_FLAG,
125 OCIRecipeBuildAlreadyPending,
126 OCIRecipeBuildRequestStatus,
127@@ -94,6 +95,45 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
128 with admin_logged_in():
129 self.assertProvides(target, IOCIRecipe)
130
131+ def test_default_distribution_on_project_pillar(self):
132+ # If OCI_RECIPE_BUILD_DISTRIBUTION flag is not set, we use Ubuntu.
133+ project = self.factory.makeProduct()
134+ oci_project = self.factory.makeOCIProject(pillar=project)
135+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
136+ self.assertEqual('ubuntu', recipe.distribution.name)
137+
138+ def test_feature_flag_distribution_on_project_pillar(self):
139+ # With the OCI_RECIPE_BUILD_DISTRIBUTION feature flag set, we should
140+ # use the distribution with the given name.
141+ distro_name = "mydistro"
142+ distribution = self.factory.makeDistribution(name=distro_name)
143+ project = self.factory.makeProduct()
144+ oci_project = self.factory.makeOCIProject(pillar=project)
145+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
146+ with FeatureFixture({OCI_RECIPE_BUILD_DISTRIBUTION: distro_name}):
147+ self.assertEqual(distribution, recipe.distribution)
148+
149+ def test_feature_flag_inexisting_distribution_on_project_pillar(self):
150+ # If we mistakenly set the flag to a non-existing distribution,
151+ # things should break explicitly.
152+ project = self.factory.makeProduct()
153+ oci_project = self.factory.makeOCIProject(pillar=project)
154+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
155+ with FeatureFixture({OCI_RECIPE_BUILD_DISTRIBUTION: "banana-distro"}):
156+ expected_msg = (
157+ "'banana-distro' is not a valid value for feature flag '%s'"
158+ % OCI_RECIPE_BUILD_DISTRIBUTION)
159+ self.assertRaisesWithContent(
160+ ValueError, expected_msg, getattr, recipe, 'distribution')
161+
162+ def test_distribution_for_distro_based_oci_project(self):
163+ # For distribution-based OCI projects, we should use OCIProject's
164+ # distribution as the recipe distribution.
165+ distribution = self.factory.makeDistribution()
166+ oci_project = self.factory.makeOCIProject(pillar=distribution)
167+ recipe = self.factory.makeOCIRecipe(oci_project=oci_project)
168+ self.assertEqual(distribution, recipe.distribution)
169+
170 def test_initial_date_last_modified(self):
171 # The initial value of date_last_modified is date_created.
172 recipe = self.factory.makeOCIRecipe(date_created=ONE_DAY_AGO)
173diff --git a/lib/lp/registry/browser/personproduct.py b/lib/lp/registry/browser/personproduct.py
174index 90f8b55..a249b27 100644
175--- a/lib/lp/registry/browser/personproduct.py
176+++ b/lib/lp/registry/browser/personproduct.py
177@@ -1,4 +1,4 @@
178-# Copyright 2009 Canonical Ltd. This software is licensed under the
179+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
180 # GNU Affero General Public License version 3 (see the file LICENSE).
181
182 """Views, menus and traversal related to PersonProducts."""
183@@ -10,19 +10,23 @@ __all__ = [
184 'PersonProductNavigation',
185 ]
186
187-
188-from zope.component import queryAdapter
189+from zope.component import (
190+ getUtility,
191+ queryAdapter,
192+ )
193 from zope.interface import implementer
194 from zope.traversing.interfaces import IPathAdapter
195
196 from lp.app.errors import NotFoundError
197 from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
198 from lp.code.interfaces.branchnamespace import get_branch_namespace
199+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
200 from lp.registry.interfaces.personproduct import IPersonProduct
201 from lp.services.webapp import (
202 canonical_url,
203 Navigation,
204 StandardLaunchpadFacets,
205+ stepthrough,
206 )
207 from lp.services.webapp.breadcrumb import Breadcrumb
208 from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
209@@ -33,6 +37,12 @@ class PersonProductNavigation(PersonTargetDefaultVCSNavigationMixin,
210 """Navigation to branches for this person/product."""
211 usedfor = IPersonProduct
212
213+ @stepthrough('+oci')
214+ def traverse_oci(self, name):
215+ oci_project = self.context.product.getOCIProject(name)
216+ return getUtility(IPersonOCIProjectFactory).create(
217+ self.context.person, oci_project)
218+
219 def traverse(self, branch_name):
220 """Look for a branch in the person/product namespace."""
221 namespace = get_branch_namespace(