Merge lp:~cjwatson/launchpad/redirect-release-uploads into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 16193
Proposed branch: lp:~cjwatson/launchpad/redirect-release-uploads
Merge into: lp:launchpad
Diff against target: 535 lines (+205/-22)
19 files modified
lib/lp/archiveuploader/nascentupload.py (+3/-0)
lib/lp/archiveuploader/tests/nascentupload-announcements.txt (+10/-0)
lib/lp/archiveuploader/tests/test_sync_notification.py (+1/-0)
lib/lp/archiveuploader/tests/test_uploadpolicy.py (+29/-0)
lib/lp/archiveuploader/tests/test_uploadprocessor.py (+33/-1)
lib/lp/archiveuploader/uploadpolicy.py (+16/-0)
lib/lp/archiveuploader/uploadprocessor.py (+1/-1)
lib/lp/registry/configure.zcml (+1/-0)
lib/lp/registry/interfaces/distribution.py (+6/-1)
lib/lp/registry/model/distribution.py (+1/-0)
lib/lp/soyuz/doc/archive.txt (+1/-1)
lib/lp/soyuz/doc/soyuz-set-of-uploads.txt (+8/-2)
lib/lp/soyuz/emailtemplates/upload-accepted.txt (+2/-0)
lib/lp/soyuz/interfaces/archive.py (+17/-1)
lib/lp/soyuz/model/archive.py (+14/-8)
lib/lp/soyuz/model/packagecopyjob.py (+6/-4)
lib/lp/soyuz/scripts/packagecopier.py (+2/-1)
lib/lp/soyuz/tests/test_archive.py (+24/-0)
lib/lp/soyuz/tests/test_packagecopyjob.py (+30/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/redirect-release-uploads
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+131155@code.launchpad.net

Commit message

If Distribution.redirect_release_uploads is set, redirect RELEASE pocket uploads to PROPOSED, and refuse some copies.

Description of the change

== Summary ==

''Summarise the problem that you're solving.''

Bug 1068071: To make raring more continuously usable, UE wants to land all uploads in raring-proposed and promote them automatically to raring following tests somewhat akin to those used by Debian's testing suite, in what amounts to a continuous integration system. We'll operate the automation, but its usefulness will be significantly enhanced by redirecting uploads automatically from raring to raring-proposed.

== Proposed fix ==

A new Distribution.redirect_release_uploads column is making its way through buildbot/QA as I type this. If it's set, then:

 * Automatically rewrite source uploads from RELEASE to PROPOSED in archiveuploader, adding a note to acceptance mails that this has happened.
 * Forbid copies into the RELEASE pocket, except for queue admins (if nothing else, such copies are used by the automation itself).

== Pre-implementation notes ==

Extensive discussions on #ubuntu-release over the last week or so. I was going to automatically rewrite some copies as well, but William convinced me that this was excessively magical and complicated the API in ways that would be troublesome in future. We'll probably end up SRUing syncpackage instead.

== Implementation details ==

The insecure upload policy seemed a sensible place to do redirects, but it does have the slight problem that setDistroSeriesAndPocket is sometimes called before we have a NascentUpload to add warnings to. I ended up stashing the warning temporarily in policy.redirect_warning, which is a little bit of a kludge.

This causes acceptance mails to have some extra bits (exemplified by the changes to nascentupload-announcements.txt) due to the normal contents of %(SUMMARY)s. I don't think this is a problem.

The copy restriction seemed most naturally done in checkUploadToPocket, although that needs to be guarded by a check_redirect=False parameter because that method is also called for build uploads which must not be redirected. A couple of places were doing their own checks for copies into the RELEASE pocket of PPAs; I couldn't see any reason why these wouldn't also want all the other checks in checkUploadToPocket - indeed, copies into the pockets forbidden there will generally cause publisher badness - other than making sure that they raised the correct exception type, so I rewrote these in terms of that method.

== LOC Rationale ==

+166. This seems cheap at the price for what should be a major improvement in the ongoing stability of Ubuntu. I have about 5600 lines of credit to help absorb this, and still have some more refactoring work to come.

== Tests ==

Probably wants the whole test suite, but in particular:

  bin/test -vvct archiveuploader -t soyuz

== Demo and Q/A ==

Flip the switch on dogfood, upload source to the RELEASE pocket, whatever its current development series is, check that it goes to PROPOSED, check that a non-queue-admin can't copy it into RELEASE, and check that a queue admin can.

== Lint ==

Pre-existing / false positives:

./lib/lp/archiveuploader/tests/nascentupload-announcements.txt
     186: want exceeds 78 characters.
     187: want exceeds 78 characters.
     660: want exceeds 78 characters.
     727: want exceeds 78 characters.
     729: want exceeds 78 characters.
     780: want exceeds 78 characters.
     782: want exceeds 78 characters.
./lib/lp/soyuz/emailtemplates/upload-accepted.txt
      14: Line has trailing whitespace.
./lib/lp/soyuz/scripts/packagecopier.py
      50: E302 expected 2 blank lines, found 1

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

Thanks for the fixes.

review: Approve (code)
Revision history for this message
William Grant (wgrant) wrote :

Actually, the archiveuploader changes look like they will override PPA and partner uploads too, which is sort of bad.

review: Needs Fixing (code)
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 'lib/lp/archiveuploader/nascentupload.py'
2--- lib/lp/archiveuploader/nascentupload.py 2012-07-03 08:04:35 +0000
3+++ lib/lp/archiveuploader/nascentupload.py 2012-10-25 09:05:26 +0000
4@@ -138,6 +138,9 @@
5 self.reject(
6 "Unable to find distroseries: %s" % self.changes.suite_name)
7
8+ if policy.redirect_warning is not None:
9+ self.warn(policy.redirect_warning)
10+
11 # Make sure the changes file name is well-formed.
12 self.run_and_reject_on_error(self.changes.checkFileName)
13
14
15=== modified file 'lib/lp/archiveuploader/tests/nascentupload-announcements.txt'
16--- lib/lp/archiveuploader/tests/nascentupload-announcements.txt 2012-03-29 07:56:15 +0000
17+++ lib/lp/archiveuploader/tests/nascentupload-announcements.txt 2012-10-25 09:05:26 +0000
18@@ -513,6 +513,11 @@
19 DEBUG
20 DEBUG ==
21 DEBUG
22+ DEBUG OK: bar_1.0.orig.tar.gz
23+ DEBUG OK: bar_1.0-4.diff.gz
24+ DEBUG OK: bar_1.0-4.dsc
25+ DEBUG -> Component: universe Section: devel
26+ DEBUG
27 DEBUG Announcing to hoary-announce@lists.ubuntu.com
28 DEBUG
29 DEBUG Thank you for your contribution to Ubuntu Linux.
30@@ -728,6 +733,11 @@
31 <BLANKLINE>
32 =3D=3D
33 <BLANKLINE>
34+ OK: bar_1.0.orig.tar.gz
35+ OK: bar_1.0-10.diff.gz
36+ OK: bar_1.0-10.dsc
37+ -> Component: universe Section: devel
38+ <BLANKLINE>
39 Announcing to hoary-announce@lists.ubuntu.com
40 <BLANKLINE>
41 Thank you for your contribution to Ubuntu Linux.
42
43=== modified file 'lib/lp/archiveuploader/tests/test_sync_notification.py'
44--- lib/lp/archiveuploader/tests/test_sync_notification.py 2012-01-06 06:07:34 +0000
45+++ lib/lp/archiveuploader/tests/test_sync_notification.py 2012-10-25 09:05:26 +0000
46@@ -38,6 +38,7 @@
47 self.distroseries = spph.distroseries
48 self.archive = spph.distroseries.main_archive
49 self.pocket = spph.pocket
50+ self.redirect_warning = None
51
52 setDistroSeriesAndPocket = FakeMethod()
53 validateUploadType = FakeMethod()
54
55=== modified file 'lib/lp/archiveuploader/tests/test_uploadpolicy.py'
56--- lib/lp/archiveuploader/tests/test_uploadpolicy.py 2012-09-13 05:14:05 +0000
57+++ lib/lp/archiveuploader/tests/test_uploadpolicy.py 2012-10-25 09:05:26 +0000
58@@ -16,6 +16,7 @@
59 InsecureUploadPolicy,
60 )
61 from lp.registry.interfaces.distribution import IDistributionSet
62+from lp.registry.interfaces.pocket import PackagePublishingPocket
63 from lp.registry.interfaces.series import SeriesStatus
64 from lp.services.database.sqlbase import flush_database_updates
65 from lp.testing import (
66@@ -193,6 +194,34 @@
67 NotFoundError, policy.setDistroSeriesAndPocket,
68 'nonexistent_security')
69
70+ def test_redirect_release_uploads_primary(self):
71+ # With the insecure policy, the
72+ # Distribution.redirect_release_uploads flag causes uploads to the
73+ # RELEASE pocket to be automatically redirected to PROPOSED.
74+ ubuntu = getUtility(IDistributionSet)["ubuntu"]
75+ with celebrity_logged_in("admin"):
76+ ubuntu.redirect_release_uploads = True
77+ flush_database_updates()
78+ insecure_policy = findPolicyByName("insecure")
79+ insecure_policy.setOptions(FakeOptions(distroseries="hoary"))
80+ self.assertEqual("hoary", insecure_policy.distroseries.name)
81+ self.assertEqual(
82+ PackagePublishingPocket.PROPOSED, insecure_policy.pocket)
83+
84+ def test_redirect_release_uploads_ppa(self):
85+ # The Distribution.redirect_release_uploads flag does not affect PPA
86+ # uploads.
87+ ubuntu = getUtility(IDistributionSet)["ubuntu"]
88+ with celebrity_logged_in("admin"):
89+ ubuntu.redirect_release_uploads = True
90+ flush_database_updates()
91+ insecure_policy = findPolicyByName("insecure")
92+ insecure_policy.archive = self.factory.makeArchive()
93+ insecure_policy.setOptions(FakeOptions(distroseries="hoary"))
94+ self.assertEqual("hoary", insecure_policy.distroseries.name)
95+ self.assertEqual(
96+ PackagePublishingPocket.RELEASE, insecure_policy.pocket)
97+
98 def setHoaryStatus(self, status):
99 ubuntu = getUtility(IDistributionSet)["ubuntu"]
100 with celebrity_logged_in("admin"):
101
102=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
103--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2012-09-27 02:53:00 +0000
104+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2012-10-25 09:05:26 +0000
105@@ -1,4 +1,4 @@
106-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
107+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
108 # GNU Affero General Public License version 3 (see the file LICENSE).
109
110 """Functional tests for uploadprocessor.py."""
111@@ -1963,6 +1963,38 @@
112 self.assertEqual("optional", ddeb.priority_name)
113 self.assertEqual(deb.priority_name, ddeb.priority_name)
114
115+ def test_redirect_release_uploads(self):
116+ # Setting the Distribution.redirect_release_uploads flag causes
117+ # release pocket uploads to be redirected to proposed.
118+ uploadprocessor = self.setupBreezyAndGetUploadProcessor(
119+ policy="insecure")
120+ self.switchToAdmin()
121+ self.ubuntu.redirect_release_uploads = True
122+ # Don't bother with announcements.
123+ self.breezy.changeslist = None
124+ self.switchToUploader()
125+ upload_dir = self.queueUpload("bar_1.0-1")
126+ self.processUpload(uploadprocessor, upload_dir)
127+ self.assertEmail(
128+ contents=["Redirecting ubuntu breezy to ubuntu breezy-proposed."],
129+ recipients=[])
130+ [queue_item] = self.breezy.getPackageUploads(
131+ status=PackageUploadStatus.NEW, name=u"bar",
132+ version=u"1.0-1", exact_match=True)
133+ self.assertEqual(PackagePublishingPocket.PROPOSED, queue_item.pocket)
134+
135+ queue_item.acceptFromQueue()
136+ pop_notifications()
137+ upload_dir = self.queueUpload("bar_1.0-2")
138+ self.processUpload(uploadprocessor, upload_dir)
139+ self.assertEmail(
140+ contents=["Redirecting ubuntu breezy to ubuntu breezy-proposed."],
141+ recipients=[])
142+ [queue_item] = self.breezy.getPackageUploads(
143+ status=PackageUploadStatus.DONE, name=u"bar",
144+ version=u"1.0-2", exact_match=True)
145+ self.assertEqual(PackagePublishingPocket.PROPOSED, queue_item.pocket)
146+
147
148 class TestUploadHandler(TestUploadProcessorBase):
149
150
151=== modified file 'lib/lp/archiveuploader/uploadpolicy.py'
152--- lib/lp/archiveuploader/uploadpolicy.py 2012-09-17 05:25:38 +0000
153+++ lib/lp/archiveuploader/uploadpolicy.py 2012-10-25 09:05:26 +0000
154@@ -32,6 +32,7 @@
155 from lp.registry.interfaces.distribution import IDistributionSet
156 from lp.registry.interfaces.pocket import PackagePublishingPocket
157 from lp.registry.interfaces.series import SeriesStatus
158+from lp.soyuz.enums import ArchivePurpose
159
160 # Number of seconds in an hour (used later)
161 HOURS = 3600
162@@ -76,6 +77,7 @@
163 name = 'abstract'
164 options = None
165 accepted_type = None # Must be defined in subclasses.
166+ redirect_warning = None
167
168 def __init__(self):
169 """Prepare a policy..."""
170@@ -201,6 +203,20 @@
171 name = 'insecure'
172 accepted_type = ArchiveUploadType.SOURCE_ONLY
173
174+ def setDistroSeriesAndPocket(self, dr_name):
175+ """Set the distroseries and pocket from the provided name.
176+
177+ The insecure policy redirects uploads to a different pocket if
178+ Distribution.redirect_release_uploads is set.
179+ """
180+ super(InsecureUploadPolicy, self).setDistroSeriesAndPocket(dr_name)
181+ if (self.archive.purpose == ArchivePurpose.PRIMARY and
182+ self.distro.redirect_release_uploads and
183+ self.pocket == PackagePublishingPocket.RELEASE):
184+ self.pocket = PackagePublishingPocket.PROPOSED
185+ self.redirect_warning = "Redirecting %s to %s-proposed." % (
186+ self.distroseries, self.distroseries)
187+
188 def rejectPPAUploads(self, upload):
189 """Insecure policy allows PPA upload."""
190 return False
191
192=== modified file 'lib/lp/archiveuploader/uploadprocessor.py'
193--- lib/lp/archiveuploader/uploadprocessor.py 2012-06-29 08:40:05 +0000
194+++ lib/lp/archiveuploader/uploadprocessor.py 2012-10-25 09:05:26 +0000
195@@ -1,4 +1,4 @@
196-# Copyright 2009 Canonical Ltd. This software is licensed under the
197+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
198 # GNU Affero General Public License version 3 (see the file LICENSE).
199
200 """Code for 'processing' 'uploads'. Also see nascentupload.py.
201
202=== modified file 'lib/lp/registry/configure.zcml'
203--- lib/lp/registry/configure.zcml 2012-10-22 10:09:48 +0000
204+++ lib/lp/registry/configure.zcml 2012-10-25 09:05:26 +0000
205@@ -1623,6 +1623,7 @@
206 official_malone
207 owner
208 package_derivatives_email
209+ redirect_release_uploads
210 summary
211 title"/>
212 <require
213
214=== modified file 'lib/lp/registry/interfaces/distribution.py'
215--- lib/lp/registry/interfaces/distribution.py 2012-10-05 05:34:13 +0000
216+++ lib/lp/registry/interfaces/distribution.py 2012-10-25 09:05:26 +0000
217@@ -1,4 +1,4 @@
218-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
219+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
220 # GNU Affero General Public License version 3 (see the file LICENSE).
221
222 """Interfaces including and related to IDistribution."""
223@@ -361,6 +361,11 @@
224 description=_("True if this distribution has sources published."),
225 readonly=True, required=False)
226
227+ redirect_release_uploads = exported(Bool(
228+ title=_("Redirect release pocket uploads"),
229+ description=_("Redirect release pocket uploads to proposed pocket"),
230+ readonly=False, required=True))
231+
232 def getArchiveIDList(archive=None):
233 """Return a list of archive IDs suitable for sqlvalues() or quote().
234
235
236=== modified file 'lib/lp/registry/model/distribution.py'
237--- lib/lp/registry/model/distribution.py 2012-09-27 18:04:35 +0000
238+++ lib/lp/registry/model/distribution.py 2012-10-25 09:05:26 +0000
239@@ -257,6 +257,7 @@
240 schema=TranslationPermission, default=TranslationPermission.OPEN)
241 active = True
242 package_derivatives_email = StringCol(notNull=False, default=None)
243+ redirect_release_uploads = BoolCol(notNull=True, default=False)
244
245 def __repr__(self):
246 displayname = self.displayname.encode('ASCII', 'backslashreplace')
247
248=== modified file 'lib/lp/soyuz/doc/archive.txt'
249--- lib/lp/soyuz/doc/archive.txt 2012-09-27 02:53:00 +0000
250+++ lib/lp/soyuz/doc/archive.txt 2012-10-25 09:05:26 +0000
251@@ -2175,7 +2175,7 @@
252 ... "package3", "1.2", cprov.archive, "updates", person=mark)
253 Traceback (most recent call last):
254 ...
255- CannotCopy: Destination pocket must be 'release' for a PPA.
256+ CannotCopy: PPA uploads must be for the RELEASE pocket.
257
258 syncSource() will always use only the latest publication of the
259 specific source, ignoring the previous ones. Multiple publications can
260
261=== modified file 'lib/lp/soyuz/doc/soyuz-set-of-uploads.txt'
262--- lib/lp/soyuz/doc/soyuz-set-of-uploads.txt 2012-10-03 08:20:24 +0000
263+++ lib/lp/soyuz/doc/soyuz-set-of-uploads.txt 2012-10-25 09:05:26 +0000
264@@ -74,7 +74,6 @@
265 Having set up that infrastructure we need to prepare a breezy distroseries
266 for the ubuntutest distribution.
267
268- >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
269 >>> from lp.registry.model.distribution import Distribution
270 >>> from lp.soyuz.enums import PackageUploadStatus
271 >>> from lp.soyuz.scripts.initialize_distroseries import (
272@@ -101,7 +100,9 @@
273
274 Add disk content for file inherited from ubuntu/breezy-autotest:
275
276- >>> from lp.services.librarianserver.testing.server import fillLibrarianFile
277+ >>> from lp.services.librarianserver.testing.server import (
278+ ... fillLibrarianFile,
279+ ... )
280 >>> fillLibrarianFile(54)
281
282 Now that the infrastructure is ready, we prepare a set of useful methods.
283@@ -345,6 +346,11 @@
284 <BLANKLINE>
285 =3D=3D
286 <BLANKLINE>
287+ OK: bar_1.0.orig.tar.gz
288+ OK: bar_1.0-4.diff.gz
289+ OK: bar_1.0-4.dsc
290+ -> Component: universe Section: devel
291+ <BLANKLINE>
292 Announcing to breezy-changes@ubuntu.com
293 <BLANKLINE>
294 Thank you for your contribution to Ubuntu Test.
295
296=== modified file 'lib/lp/soyuz/emailtemplates/upload-accepted.txt'
297--- lib/lp/soyuz/emailtemplates/upload-accepted.txt 2011-12-18 23:30:56 +0000
298+++ lib/lp/soyuz/emailtemplates/upload-accepted.txt 2012-10-25 09:05:26 +0000
299@@ -5,6 +5,8 @@
300
301 ==
302
303+%(SUMMARY)s
304+
305 %(ANNOUNCE)s
306
307 Thank you for your contribution to %(DISTRO)s.
308
309=== modified file 'lib/lp/soyuz/interfaces/archive.py'
310--- lib/lp/soyuz/interfaces/archive.py 2012-10-23 12:36:50 +0000
311+++ lib/lp/soyuz/interfaces/archive.py 2012-10-25 09:05:26 +0000
312@@ -42,6 +42,7 @@
313 'NoTokensForTeams',
314 'PocketNotFound',
315 'PriorityNotFound',
316+ 'RedirectedPocket',
317 'SectionNotFound',
318 'VersionRequiresName',
319 'default_name_by_purpose',
320@@ -206,6 +207,19 @@
321 "'%s' state." % (pocket.name, distroseries.status.name))
322
323
324+@error_status(httplib.FORBIDDEN)
325+class RedirectedPocket(Exception):
326+ """Returned for a pocket that would normally be redirected to another.
327+
328+ This is used in contexts (e.g. copies) where actually doing the
329+ redirection would be Too Much Magic."""
330+
331+ def __init__(self, distroseries, pocket, preferred):
332+ Exception.__init__(self,
333+ "Not permitted to upload directly to %s; try %s instead." %
334+ (distroseries.getSuite(pocket), distroseries.getSuite(preferred)))
335+
336+
337 class CannotUploadToPPA(CannotUploadToArchive):
338 """Raised when a person cannot upload to a PPA."""
339
340@@ -598,11 +612,13 @@
341 Return True if the upload is allowed and False if denied.
342 """
343
344- def checkUploadToPocket(distroseries, pocket):
345+ def checkUploadToPocket(distroseries, pocket, person=None):
346 """Check if an upload to a particular archive and pocket is possible.
347
348 :param distroseries: A `IDistroSeries`
349 :param pocket: A `PackagePublishingPocket`
350+ :param person: Check for redirected pockets if this person is not a
351+ queue admin.
352 :return: Reason why uploading is not possible or None
353 """
354
355
356=== modified file 'lib/lp/soyuz/model/archive.py'
357--- lib/lp/soyuz/model/archive.py 2012-10-24 09:25:16 +0000
358+++ lib/lp/soyuz/model/archive.py 2012-10-25 09:05:26 +0000
359@@ -148,6 +148,7 @@
360 NoSuchPPA,
361 NoTokensForTeams,
362 PocketNotFound,
363+ RedirectedPocket,
364 validate_external_dependencies,
365 VersionRequiresName,
366 )
367@@ -1266,7 +1267,7 @@
368 # Allow anything else.
369 return True
370
371- def checkUploadToPocket(self, distroseries, pocket):
372+ def checkUploadToPocket(self, distroseries, pocket, person=None):
373 """See `IArchive`."""
374 if self.is_partner:
375 if pocket not in (
376@@ -1282,6 +1283,14 @@
377 # existing builds after a series is released.
378 return
379 else:
380+ if (self.purpose == ArchivePurpose.PRIMARY and
381+ person is not None and
382+ not self.canAdministerQueue(
383+ person, pocket=pocket, distroseries=distroseries) and
384+ pocket == PackagePublishingPocket.RELEASE and
385+ self.distribution.redirect_release_uploads):
386+ return RedirectedPocket(
387+ distroseries, pocket, PackagePublishingPocket.PROPOSED)
388 # Uploads to the partner archive are allowed in any distroseries
389 # state.
390 # XXX julian 2005-05-29 bug=117557:
391@@ -1799,14 +1808,11 @@
392 from lp.soyuz.scripts.packagecopier import do_copy
393
394 pocket = self._text_to_pocket(to_pocket)
395- # Fail immediately if the destination pocket is not Release and
396- # this archive is a PPA.
397- if self.is_ppa and pocket != PackagePublishingPocket.RELEASE:
398- raise CannotCopy(
399- "Destination pocket must be 'release' for a PPA.")
400-
401- # Now convert the to_series string to a real distroseries.
402 series = self._text_to_series(to_series)
403+ reason = self.checkUploadToPocket(series, pocket, person=person)
404+ if reason:
405+ # Wrap any forbidden-pocket error in CannotCopy.
406+ raise CannotCopy(unicode(reason))
407
408 # Perform the copy, may raise CannotCopy. Don't do any further
409 # permission checking: this method is protected by
410
411=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
412--- lib/lp/soyuz/model/packagecopyjob.py 2012-10-23 12:36:50 +0000
413+++ lib/lp/soyuz/model/packagecopyjob.py 2012-10-25 09:05:26 +0000
414@@ -611,10 +611,12 @@
415 :raise CannotCopy: If the copy fails for a reason that the user
416 can deal with.
417 """
418- if self.target_archive.is_ppa:
419- if self.target_pocket != PackagePublishingPocket.RELEASE:
420- raise CannotCopy(
421- "Destination pocket must be 'release' for a PPA.")
422+ reason = self.target_archive.checkUploadToPocket(
423+ self.target_distroseries, self.target_pocket,
424+ person=self.requester)
425+ if reason:
426+ # Wrap any forbidden-pocket error in CannotCopy.
427+ raise CannotCopy(unicode(reason))
428
429 source_package = self.findSourcePublication()
430
431
432=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
433--- lib/lp/soyuz/scripts/packagecopier.py 2012-09-28 06:15:58 +0000
434+++ lib/lp/soyuz/scripts/packagecopier.py 2012-10-25 09:05:26 +0000
435@@ -205,7 +205,8 @@
436 destination_component = None
437
438 # Is the destination pocket open at all?
439- reason = archive.checkUploadToPocket(dest_series, pocket)
440+ reason = archive.checkUploadToPocket(
441+ dest_series, pocket, person=person)
442 if reason is not None:
443 raise CannotCopy(reason)
444
445
446=== modified file 'lib/lp/soyuz/tests/test_archive.py'
447--- lib/lp/soyuz/tests/test_archive.py 2012-10-24 09:25:16 +0000
448+++ lib/lp/soyuz/tests/test_archive.py 2012-10-25 09:05:26 +0000
449@@ -66,6 +66,7 @@
450 NoRightsForArchive,
451 NoRightsForComponent,
452 NoSuchPPA,
453+ RedirectedPocket,
454 VersionRequiresName,
455 )
456 from lp.soyuz.interfaces.archivearch import IArchiveArchSet
457@@ -713,6 +714,29 @@
458 archive.checkUploadToPocket(
459 distroseries, PackagePublishingPocket.RELEASE))
460
461+ def test_checkUploadToPocket_handles_redirects(self):
462+ # Uploading to the release pocket is disallowed if
463+ # Distribution.redirect_release_uploads is set.
464+ archive, distroseries = self.makeArchiveAndActiveDistroSeries(
465+ purpose=ArchivePurpose.PRIMARY)
466+ with person_logged_in(archive.distribution.owner):
467+ archive.distribution.redirect_release_uploads = True
468+ person = self.factory.makePerson()
469+ self.assertIsInstance(
470+ archive.checkUploadToPocket(
471+ distroseries, PackagePublishingPocket.RELEASE, person=person),
472+ RedirectedPocket)
473+ # The proposed pocket is unaffected.
474+ self.assertIsNone(
475+ archive.checkUploadToPocket(
476+ distroseries, PackagePublishingPocket.PROPOSED, person=person))
477+ # Queue admins bypass this check.
478+ with person_logged_in(archive.distribution.owner):
479+ archive.newQueueAdmin(person, "main")
480+ self.assertIsNone(
481+ archive.checkUploadToPocket(
482+ distroseries, PackagePublishingPocket.RELEASE, person=person))
483+
484 def test_checkUpload_package_permission(self):
485 archive, distroseries = self.makeArchiveAndActiveDistroSeries(
486 purpose=ArchivePurpose.PRIMARY)
487
488=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
489--- lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-23 12:36:50 +0000
490+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-25 09:05:26 +0000
491@@ -416,8 +416,7 @@
492 self.assertEqual(JobStatus.FAILED, job.status)
493
494 self.assertEqual(
495- "Destination pocket must be 'release' for a PPA.",
496- job.error_message)
497+ "PPA uploads must be for the RELEASE pocket.", job.error_message)
498
499 def assertOopsRecorded(self, job):
500 self.assertEqual(JobStatus.FAILED, job.status)
501@@ -451,6 +450,35 @@
502 transaction.abort()
503 self.assertOopsRecorded(job)
504
505+ def test_target_primary_redirects(self):
506+ # For primary archives with redirect_release_uploads set, ordinary
507+ # uploaders may not copy directly into the release pocket.
508+ job = create_proper_job(self.factory)
509+ job.target_archive.distribution.redirect_release_uploads = True
510+ # CannotCopy exceptions when copying into a primary archive are
511+ # swallowed, but reportFailure is still called.
512+ naked_job = removeSecurityProxy(job)
513+ naked_job.reportFailure = FakeMethod()
514+ transaction.commit()
515+ self.runJob(job)
516+ self.assertEqual(1, naked_job.reportFailure.call_count)
517+
518+ def test_target_primary_queue_admin_bypasses_redirect(self):
519+ # For primary archives with redirect_release_uploads set, queue
520+ # admins may copy directly into the release pocket anyway.
521+ job = create_proper_job(self.factory)
522+ job.target_archive.distribution.redirect_release_uploads = True
523+ with person_logged_in(job.target_archive.owner):
524+ job.target_archive.newPocketQueueAdmin(
525+ job.requester, PackagePublishingPocket.RELEASE)
526+ # CannotCopy exceptions when copying into a primary archive are
527+ # swallowed, but reportFailure is still called.
528+ naked_job = removeSecurityProxy(job)
529+ naked_job.reportFailure = FakeMethod()
530+ transaction.commit()
531+ self.runJob(job)
532+ self.assertEqual(0, naked_job.reportFailure.call_count)
533+
534 def test_run(self):
535 # A proper test run synchronizes packages.
536