Merge lp:~cjwatson/launchpad/bpb-external-dependencies into lp:launchpad

Proposed by Colin Watson on 2016-01-06
Status: Merged
Merged at revision: 17881
Proposed branch: lp:~cjwatson/launchpad/bpb-external-dependencies
Merge into: lp:launchpad
Diff against target: 259 lines (+126/-9)
7 files modified
lib/lp/security.py (+19/-1)
lib/lp/soyuz/configure.zcml (+6/-2)
lib/lp/soyuz/interfaces/archive.py (+2/-2)
lib/lp/soyuz/interfaces/binarypackagebuild.py (+21/-2)
lib/lp/soyuz/model/binarypackagebuild.py (+17/-1)
lib/lp/soyuz/stories/webservice/xx-builds.txt (+1/-0)
lib/lp/soyuz/tests/test_binarypackagebuild.py (+60/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/bpb-external-dependencies
Reviewer Review Type Date Requested Status
William Grant code 2016-01-06 Approve on 2016-01-06
Review via email: mp+281744@code.launchpad.net

Commit message

Allow PPA admins to set external_dependencies on individual binary package builds.

Description of the change

Allow PPA admins to set external_dependencies on individual binary package builds, allowing them to bootstrap cyclic dependencies without needing a bootstrap archive sources.list.d file in the chroot.

Using launchpad.Moderate is a little odd, but no other existing permission on that object was suitable, and this doesn't seem completely wrong. The alternatives that I can think of would be opening this facility up to buildd admins, or introducing a new role-based permission for PPA admins.

To post a comment you must log in.
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 'lib/lp/security.py'
2--- lib/lp/security.py 2015-12-02 02:15:45 +0000
3+++ lib/lp/security.py 2016-01-06 16:01:07 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Security policies for using content objects."""
10@@ -1957,6 +1957,24 @@
11 return auth_spr.checkUnauthenticated()
12
13
14+class ModerateBinaryPackageBuild(ViewBinaryPackageBuild):
15+ permission = 'launchpad.Moderate'
16+
17+ def checkAuthenticated(self, user):
18+ # Only people who can see the build and administer its archive can
19+ # edit restricted attributes of builds. (Currently this allows
20+ # setting BinaryPackageBuild.external_dependencies; people who can
21+ # administer the archive can already achieve the same effect by
22+ # setting Archive.external_dependencies.)
23+ return (
24+ super(ModerateBinaryPackageBuild, self).checkAuthenticated(
25+ user) and
26+ AdminArchive(self.obj.archive).checkAuthenticated(user))
27+
28+ def checkUnauthenticated(self, user):
29+ return False
30+
31+
32 class ViewTranslationTemplatesBuild(DelegatedAuthorization):
33 """Permission to view an `ITranslationTemplatesBuild`.
34
35
36=== modified file 'lib/lp/soyuz/configure.zcml'
37--- lib/lp/soyuz/configure.zcml 2015-11-26 15:46:38 +0000
38+++ lib/lp/soyuz/configure.zcml 2016-01-06 16:01:07 +0000
39@@ -1,4 +1,4 @@
40-<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
41+<!-- Copyright 2009-2016 Canonical Ltd. This software is licensed under the
42 GNU Affero General Public License version 3 (see the file LICENSE).
43 -->
44
45@@ -467,11 +467,15 @@
46 class="lp.soyuz.model.binarypackagebuild.BinaryPackageBuild">
47 <require
48 permission="launchpad.View"
49- interface="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildView"/>
50+ interface="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildView
51+ lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildRestricted"/>
52 <require
53 permission="launchpad.Edit"
54 interface="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildEdit"/>
55 <require
56+ permission="launchpad.Moderate"
57+ set_schema="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildRestricted"/>
58+ <require
59 permission="launchpad.Admin"
60 interface="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuildAdmin"/>
61 </class>
62
63=== modified file 'lib/lp/soyuz/interfaces/archive.py'
64--- lib/lp/soyuz/interfaces/archive.py 2015-09-25 14:18:28 +0000
65+++ lib/lp/soyuz/interfaces/archive.py 2016-01-06 16:01:07 +0000
66@@ -1,4 +1,4 @@
67-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
68+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
69 # GNU Affero General Public License version 3 (see the file LICENSE).
70
71 """Archive interfaces."""
72@@ -641,7 +641,7 @@
73 Text(title=_("External dependencies"), required=False,
74 readonly=False, description=_(
75 "Newline-separated list of repositories to be used to retrieve "
76- "any external build dependencies when building packages in the "
77+ "any external build-dependencies when building packages in the "
78 "archive, in the format:\n"
79 "deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] "
80 "[components]\n"
81
82=== modified file 'lib/lp/soyuz/interfaces/binarypackagebuild.py'
83--- lib/lp/soyuz/interfaces/binarypackagebuild.py 2015-05-14 08:50:41 +0000
84+++ lib/lp/soyuz/interfaces/binarypackagebuild.py 2016-01-06 16:01:07 +0000
85@@ -1,4 +1,4 @@
86-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
87+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
88 # GNU Affero General Public License version 3 (see the file LICENSE).
89
90 """BinaryPackageBuild interfaces."""
91@@ -266,6 +266,25 @@
92 """
93
94
95+class IBinaryPackageBuildRestricted(Interface):
96+ """Restricted `IBinaryPackageBuild` attributes.
97+
98+ These attributes need launchpad.View to see, and launchpad.Moderate to
99+ change.
100+ """
101+ external_dependencies = exported(
102+ Text(
103+ title=_("External dependencies"), required=False, readonly=False,
104+ description=_(
105+ "Newline-separated list of repositories to be used to "
106+ "retrieve any external build-dependencies when performing "
107+ "this build, in the format:\n"
108+ "deb http[s]://[user:pass@]<host>[/path] series[-pocket] "
109+ "[components]\n"
110+ "This is intended for bootstrapping build-dependency loops.")),
111+ as_of="devel")
112+
113+
114 class IBinaryPackageBuildAdmin(Interface):
115 """A Build interface for items requiring launchpad.Admin."""
116
117@@ -277,7 +296,7 @@
118
119 class IBinaryPackageBuild(
120 IBinaryPackageBuildView, IBinaryPackageBuildEdit,
121- IBinaryPackageBuildAdmin):
122+ IBinaryPackageBuildRestricted, IBinaryPackageBuildAdmin):
123 """A Build interface"""
124 export_as_webservice_entry(singular_name='build', plural_name='builds')
125
126
127=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
128--- lib/lp/soyuz/model/binarypackagebuild.py 2015-12-08 23:40:07 +0000
129+++ lib/lp/soyuz/model/binarypackagebuild.py 2016-01-06 16:01:07 +0000
130@@ -1,4 +1,4 @@
131-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
132+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
133 # GNU Affero General Public License version 3 (see the file LICENSE).
134
135 __metaclass__ = type
136@@ -86,6 +86,10 @@
137 PackagePublishingStatus,
138 )
139 from lp.soyuz.adapters.buildarch import determine_architectures_to_build
140+from lp.soyuz.interfaces.archive import (
141+ InvalidExternalDependencies,
142+ validate_external_dependencies,
143+ )
144 from lp.soyuz.interfaces.binarypackagebuild import (
145 BuildSetStatus,
146 CannotBeRescored,
147@@ -149,6 +153,14 @@
148 or not processor.supports_nonvirtualized)
149
150
151+def storm_validate_external_dependencies(build, attr, value):
152+ assert attr == 'external_dependencies'
153+ errors = validate_external_dependencies(value)
154+ if len(errors) > 0:
155+ raise InvalidExternalDependencies(errors)
156+ return value
157+
158+
159 @implementer(IBinaryPackageBuild)
160 class BinaryPackageBuild(PackageBuildMixin, SQLBase):
161 _table = 'BinaryPackageBuild'
162@@ -213,6 +225,10 @@
163 source_package_name = Reference(
164 source_package_name_id, 'SourcePackageName.id')
165
166+ external_dependencies = Unicode(
167+ name='external_dependencies',
168+ validator=storm_validate_external_dependencies)
169+
170 def getLatestSourcePublication(self):
171 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
172 store = Store.of(self)
173
174=== modified file 'lib/lp/soyuz/stories/webservice/xx-builds.txt'
175--- lib/lp/soyuz/stories/webservice/xx-builds.txt 2015-05-12 01:43:31 +0000
176+++ lib/lp/soyuz/stories/webservice/xx-builds.txt 2016-01-06 16:01:07 +0000
177@@ -122,6 +122,7 @@
178 dependencies: None
179 distribution_link: u'http://.../ubuntu'
180 duration: u'0:01:20'
181+ external_dependencies: None
182 pocket: u'Release'
183 resource_type_link: u'http://.../#build'
184 score: None
185
186=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
187--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2015-12-08 23:40:07 +0000
188+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2016-01-06 16:01:07 +0000
189@@ -1,4 +1,4 @@
190-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
191+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
192 # GNU Affero General Public License version 3 (see the file LICENSE).
193
194 """Test Build features."""
195@@ -562,6 +562,65 @@
196 entry = self.webservice.get(build_url, api_version='devel').jsonBody()
197 self.assertEqual(name, entry['source_package_name'])
198
199+ def test_external_dependencies_random_user(self):
200+ # Normal users can look but not touch.
201+ person = self.factory.makePerson()
202+ build_url = api_url(self.build)
203+ logout()
204+ webservice = webservice_for_person(
205+ person, permission=OAuthPermission.WRITE_PUBLIC)
206+ entry = webservice.get(build_url, api_version="devel").jsonBody()
207+ self.assertIsNone(entry["external_dependencies"])
208+ response = webservice.patch(
209+ entry["self_link"], "application/json",
210+ dumps({"external_dependencies": "random"}))
211+ self.assertEqual(401, response.status)
212+
213+ def test_external_dependencies_owner(self):
214+ # Normal archive owners can look but not touch.
215+ build_url = api_url(self.build)
216+ logout()
217+ entry = self.webservice.get(build_url, api_version="devel").jsonBody()
218+ self.assertIsNone(entry["external_dependencies"])
219+ response = self.webservice.patch(
220+ entry["self_link"], "application/json",
221+ dumps({"external_dependencies": "random"}))
222+ self.assertEqual(401, response.status)
223+
224+ def test_external_dependencies_ppa_owner_invalid(self):
225+ # PPA admins can look and touch.
226+ ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
227+ ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
228+ build_url = api_url(self.build)
229+ logout()
230+ webservice = webservice_for_person(
231+ ppa_admin, permission=OAuthPermission.WRITE_PUBLIC)
232+ entry = webservice.get(build_url, api_version="devel").jsonBody()
233+ self.assertIsNone(entry["external_dependencies"])
234+ response = webservice.patch(
235+ entry["self_link"], "application/json",
236+ dumps({"external_dependencies": "random"}))
237+ self.assertEqual(400, response.status)
238+ self.assertIn("Invalid external dependencies", response.body)
239+
240+ def test_external_dependencies_ppa_owner_valid(self):
241+ # PPA admins can look and touch.
242+ ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
243+ ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
244+ build_url = api_url(self.build)
245+ logout()
246+ webservice = webservice_for_person(
247+ ppa_admin, permission=OAuthPermission.WRITE_PUBLIC)
248+ entry = webservice.get(build_url, api_version="devel").jsonBody()
249+ self.assertIsNone(entry["external_dependencies"])
250+ dependencies = "deb http://example.org suite components"
251+ response = webservice.patch(
252+ entry["self_link"], "application/json",
253+ dumps({"external_dependencies": dependencies}))
254+ self.assertEqual(209, response.status)
255+ self.assertEqual(
256+ dependencies, response.jsonBody()["external_dependencies"])
257+
258
259 class TestPostprocessCandidate(TestCaseWithFactory):
260