Merge lp:~cjwatson/launchpad/uefi-copy-no-auto-approve into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 16021
Proposed branch: lp:~cjwatson/launchpad/uefi-copy-no-auto-approve
Merge into: lp:launchpad
Diff against target: 390 lines (+109/-113)
5 files modified
lib/lp/soyuz/scripts/custom_uploads_copier.py (+23/-25)
lib/lp/soyuz/scripts/packagecopier.py (+3/-1)
lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py (+73/-80)
lib/lp/testing/factory.py (+5/-4)
lib/lp/testing/tests/test_factory.py (+5/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/uefi-copy-no-auto-approve
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+126128@code.launchpad.net

Commit message

Explicitly select the target archive when creating CustomUploadCopiers, rather than trying to infer it and thereby refusing to copy out of PPAs.

Description of the change

== Summary ==

Bug 1036616: we can't stage custom uploads in PPAs, because the custom uploads copier doesn't work quite right. This is about to become very important to Ubuntu Engineering in terms of how we handle UEFI secure boot: we'll need it for security updates post-release, and it appears that we may need it very soon pre-release as well for signed kernels because the workflow for uploading kernels to the development series involves staging them in a PPA.

== Proposed fix ==

As described in the bug report, I first had to pre-emptively secure against the ability to bypass the forced UNAPPROVED logic for UEFI custom uploads to the primary archive by way of copying them from a PPA.

After that, the problem was that CustomUploadCopier.getTargetArchive was just wrong. It was trying to infer the target archive based on the series and the original archive, always selecting one with the same purpose. Obviously that won't work for copying from a PPA to the primary archive. Furthermore, this convoluted logic was unnecessary anyway. There are exactly two non-test callers of CustomUploadCopier, one in lib/lp/archivepublisher/scripts/publish_ftpmaster.py (which is always copying between RELEASE pockets of two series in the same distribution, and thus always within the same archive) and the other in lib/lp/soyuz/scripts/packagecopier.py which already knows the target archive.

== Tests ==

bin/test -vvct lp.archivepublisher.tests.test_publish_ftpmaster -t lp.soyuz.scripts.tests.test_copypackage -t lp.soyuz.scripts.tests.test_custom_uploads_copier

== Demo and Q/A ==

On dogfood, attempt to copy an efilinux upload from https://dogfood.launchpad.net/~cjwatson/+archive/ppa/+packages to another PPA and to the primary archive. Both should work and the custom upload should be copied. In the case of copying to the primary archive, it should land in the UNAPPROVED queue.

To post a comment you must log in.
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/soyuz/scripts/custom_uploads_copier.py'
2--- lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-07-03 17:20:05 +0000
3+++ lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-09-24 23:55:27 +0000
4@@ -14,8 +14,6 @@
5
6 from operator import attrgetter
7
8-from zope.component import getUtility
9-
10 from lp.archivepublisher.ddtp_tarball import DdtpTarballUpload
11 from lp.archivepublisher.debian_installer import DebianInstallerUpload
12 from lp.archivepublisher.dist_upgrader import DistUpgraderUpload
13@@ -23,10 +21,6 @@
14 from lp.registry.interfaces.pocket import PackagePublishingPocket
15 from lp.services.database.bulk import load_referencing
16 from lp.soyuz.enums import PackageUploadCustomFormat
17-from lp.soyuz.interfaces.archive import (
18- IArchiveSet,
19- MAIN_ARCHIVE_PURPOSES,
20- )
21 from lp.soyuz.model.queue import PackageUploadCustom
22
23
24@@ -47,14 +41,27 @@
25 }
26
27 def __init__(self, target_series,
28- target_pocket=PackagePublishingPocket.RELEASE):
29+ target_pocket=PackagePublishingPocket.RELEASE,
30+ target_archive=None):
31 self.target_series = target_series
32 self.target_pocket = target_pocket
33+ self.target_archive = target_archive
34
35 def isCopyable(self, upload):
36 """Is `upload` the kind of `PackageUploadCustom` that we can copy?"""
37 return upload.customformat in self.copyable_types
38
39+ def autoApprove(self, custom):
40+ """Can `custom` be automatically approved?"""
41+ # XXX cjwatson 2012-08-16: This more or less duplicates
42+ # BuildDaemonUploadPolicy.autoApprove/CustomUploadFile.autoApprove.
43+ if custom.packageupload.archive.is_ppa:
44+ return True
45+ # UEFI uploads are signed, and must therefore be approved by a human.
46+ if custom.customformat == PackageUploadCustomFormat.UEFI:
47+ return False
48+ return True
49+
50 def getCandidateUploads(self, source_series,
51 source_pocket=PackagePublishingPocket.RELEASE):
52 """Find custom uploads that may need copying."""
53@@ -99,19 +106,6 @@
54 latest_uploads.setdefault(key, upload)
55 return latest_uploads
56
57- def getTargetArchive(self, original_archive):
58- """Find counterpart of `original_archive` in `self.target_series`.
59-
60- :param original_archive: The `Archive` that the original upload went
61- into. If this is not a primary, partner, or debug archive,
62- None is returned.
63- :return: The `Archive` of the same purpose for `self.target_series`.
64- """
65- if original_archive.purpose not in MAIN_ARCHIVE_PURPOSES:
66- return None
67- return getUtility(IArchiveSet).getByDistroPurpose(
68- self.target_series.distribution, original_archive.purpose)
69-
70 def isObsolete(self, upload, target_uploads):
71 """Is `upload` superseded by one that the target series already has?
72
73@@ -123,16 +117,20 @@
74
75 def copyUpload(self, original_upload):
76 """Copy `original_upload` into `self.target_series`."""
77- target_archive = self.getTargetArchive(
78- original_upload.packageupload.archive)
79- if target_archive is None:
80- return None
81+ if self.target_archive is None:
82+ # Copy within the same archive.
83+ target_archive = original_upload.packageupload.archive
84+ else:
85+ target_archive = self.target_archive
86 package_upload = self.target_series.createQueueEntry(
87 self.target_pocket, target_archive,
88 changes_file_alias=original_upload.packageupload.changesfile)
89 custom = package_upload.addCustom(
90 original_upload.libraryfilealias, original_upload.customformat)
91- package_upload.setAccepted()
92+ if self.autoApprove(custom):
93+ package_upload.setAccepted()
94+ else:
95+ package_upload.setUnapproved()
96 return custom
97
98 def copy(self, source_series,
99
100=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
101--- lib/lp/soyuz/scripts/packagecopier.py 2012-08-08 16:34:39 +0000
102+++ lib/lp/soyuz/scripts/packagecopier.py 2012-09-24 23:55:27 +0000
103@@ -45,6 +45,7 @@
104 )
105 from lp.soyuz.scripts.custom_uploads_copier import CustomUploadsCopier
106
107+
108 # XXX cprov 2009-06-12: this function should be incorporated in
109 # IPublishing.
110 def update_files_privacy(pub_record):
111@@ -785,7 +786,8 @@
112 if custom_files:
113 # Custom uploads aren't modelled as publication history records, so
114 # we have to send these through the upload queue.
115- custom_copier = CustomUploadsCopier(series, target_pocket=pocket)
116+ custom_copier = CustomUploadsCopier(
117+ series, target_pocket=pocket, target_archive=archive)
118 for custom in custom_files:
119 if custom_copier.isCopyable(custom):
120 custom_copier.copyUpload(custom)
121
122=== modified file 'lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py'
123--- lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-07-03 17:20:05 +0000
124+++ lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-09-24 23:55:27 +0000
125@@ -8,11 +8,9 @@
126 from lp.registry.interfaces.pocket import PackagePublishingPocket
127 from lp.registry.interfaces.series import SeriesStatus
128 from lp.soyuz.enums import (
129- ArchivePurpose,
130 PackageUploadCustomFormat,
131 PackageUploadStatus,
132 )
133-from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES
134 from lp.soyuz.scripts.custom_uploads_copier import CustomUploadsCopier
135 from lp.testing import TestCaseWithFactory
136 from lp.testing.fakemethod import FakeMethod
137@@ -96,8 +94,7 @@
138 # If extractSeriesKey returns None, getKey also returns None.
139 copier = CustomUploadsCopier(FakeDistroSeries())
140 copier.extractSeriesKey = FakeMethod()
141- self.assertIs(
142- None,
143+ self.assertIsNone(
144 copier.getKey(FakeUpload(
145 PackageUploadCustomFormat.DEBIAN_INSTALLER,
146 "bad-filename.tar")))
147@@ -109,7 +106,7 @@
148 # Alas, PackageUploadCustom relies on the Librarian.
149 layer = LaunchpadZopelessLayer
150
151- def makeUpload(self, distroseries=None, pocket=None,
152+ def makeUpload(self, distroseries=None, archive=None, pocket=None,
153 custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
154 version=None, arch=None, component=None):
155 """Create a `PackageUploadCustom`."""
156@@ -128,8 +125,8 @@
157 arch = self.factory.getUniqueString()
158 filename = "%s.tar.gz" % "_".join([package_name, version, arch])
159 package_upload = self.factory.makeCustomPackageUpload(
160- distroseries=distroseries, pocket=pocket, custom_type=custom_type,
161- filename=filename)
162+ distroseries=distroseries, archive=archive, pocket=pocket,
163+ custom_type=custom_type, filename=filename)
164 return package_upload.customfiles[0]
165
166 def test_copies_custom_upload(self):
167@@ -241,10 +238,7 @@
168 source_series, custom_type=PackageUploadCustomFormat.DIST_UPGRADER,
169 arch='mips')
170 copier = CustomUploadsCopier(FakeDistroSeries())
171- expected_key = (
172- PackageUploadCustomFormat.DIST_UPGRADER,
173- 'mips',
174- )
175+ expected_key = (PackageUploadCustomFormat.DIST_UPGRADER, 'mips')
176 self.assertEqual(expected_key, copier.getKey(upload))
177
178 def test_getKey_ddtp_includes_format_and_component(self):
179@@ -255,10 +249,7 @@
180 source_series, custom_type=PackageUploadCustomFormat.DDTP_TARBALL,
181 component='restricted')
182 copier = CustomUploadsCopier(FakeDistroSeries())
183- expected_key = (
184- PackageUploadCustomFormat.DDTP_TARBALL,
185- 'restricted',
186- )
187+ expected_key = (PackageUploadCustomFormat.DDTP_TARBALL, 'restricted')
188 self.assertEqual(expected_key, copier.getKey(upload))
189
190 def test_getLatestUploads_indexes_uploads_by_key(self):
191@@ -298,60 +289,6 @@
192 self.assertContentEqual(
193 uploads[-1:], copier.getLatestUploads(source_series).values())
194
195- def test_getTargetArchive_on_same_distro_is_same_archive(self):
196- # When copying within the same distribution, getTargetArchive
197- # always returns the same archive you feed it.
198- distro = self.factory.makeDistribution()
199- archives = [
200- self.factory.makeArchive(distribution=distro, purpose=purpose)
201- for purpose in MAIN_ARCHIVE_PURPOSES]
202- copier = CustomUploadsCopier(self.factory.makeDistroSeries(distro))
203- self.assertEqual(
204- archives,
205- [copier.getTargetArchive(archive) for archive in archives])
206-
207- def test_getTargetArchive_returns_None_if_not_distribution_archive(self):
208- # getTargetArchive returns None for any archive that is not a
209- # distribution archive, regardless of whether the target series
210- # has an equivalent.
211- distro = self.factory.makeDistribution()
212- archives = [
213- self.factory.makeArchive(distribution=distro, purpose=purpose)
214- for purpose in ArchivePurpose.items
215- if purpose not in MAIN_ARCHIVE_PURPOSES]
216- copier = CustomUploadsCopier(self.factory.makeDistroSeries(distro))
217- self.assertEqual(
218- [None] * len(archives),
219- [copier.getTargetArchive(archive) for archive in archives])
220-
221- def test_getTargetArchive_finds_matching_archive(self):
222- # When copying across archives, getTargetArchive looks for an
223- # archive for the target series with the same purpose as the
224- # original archive.
225- source_series = self.factory.makeDistroSeries()
226- source_archive = self.factory.makeArchive(
227- distribution=source_series.distribution,
228- purpose=ArchivePurpose.PARTNER)
229- target_series = self.factory.makeDistroSeries()
230- target_archive = self.factory.makeArchive(
231- distribution=target_series.distribution,
232- purpose=ArchivePurpose.PARTNER)
233-
234- copier = CustomUploadsCopier(target_series)
235- self.assertEqual(
236- target_archive, copier.getTargetArchive(source_archive))
237-
238- def test_getTargetArchive_returns_None_if_no_archive_matches(self):
239- # If the target series has no archive to match the archive that
240- # the original upload was far, it returns None.
241- source_series = self.factory.makeDistroSeries()
242- source_archive = self.factory.makeArchive(
243- distribution=source_series.distribution,
244- purpose=ArchivePurpose.PARTNER)
245- target_series = self.factory.makeDistroSeries()
246- copier = CustomUploadsCopier(target_series)
247- self.assertIs(None, copier.getTargetArchive(source_archive))
248-
249 def test_isObsolete_returns_False_if_no_equivalent_in_target(self):
250 # isObsolete returns False if the upload in question has no
251 # equivalent in the target series.
252@@ -393,7 +330,8 @@
253 # original, but for the target series.
254 original_upload = self.makeUpload()
255 target_series = self.factory.makeDistroSeries()
256- copier = CustomUploadsCopier(target_series)
257+ copier = CustomUploadsCopier(
258+ target_series, target_archive=target_series.main_archive)
259 copied_upload = copier.copyUpload(original_upload)
260 self.assertEqual([copied_upload], list_custom_uploads(target_series))
261 self.assertNotEqual(
262@@ -443,13 +381,68 @@
263 self.assertEqual(
264 PackageUploadStatus.ACCEPTED, copied_upload.packageupload.status)
265
266- def test_copyUpload_does_not_copy_if_no_archive_matches(self):
267- # If getTargetArchive does not find an appropriate target
268- # archive, copyUpload does nothing.
269- source_series = self.factory.makeDistroSeries()
270- upload = self.makeUpload(distroseries=source_series)
271- target_series = self.factory.makeDistroSeries()
272- copier = CustomUploadsCopier(target_series)
273- copier.getTargetArchive = FakeMethod(result=None)
274- self.assertIs(None, copier.copyUpload(upload))
275- self.assertEqual([], list_custom_uploads(target_series))
276+ def test_copyUpload_unapproves_uefi(self):
277+ # Copies of UEFI custom uploads to a primary archive are set to
278+ # UNAPPROVED, since they will normally end up being signed.
279+ original_upload = self.makeUpload(
280+ custom_type=PackageUploadCustomFormat.UEFI)
281+ target_series = self.factory.makeDistroSeries()
282+ copier = CustomUploadsCopier(target_series)
283+ copied_upload = copier.copyUpload(original_upload)
284+ self.assertEqual(
285+ PackageUploadStatus.UNAPPROVED, copied_upload.packageupload.status)
286+
287+ def test_copyUpload_approves_uefi_to_ppa(self):
288+ # Copies of UEFI custom uploads to a PPA are automatically accepted,
289+ # since PPAs have much more limited upload permissions than the main
290+ # archive, and in any case PPAs do not have an upload approval
291+ # workflow.
292+ original_upload = self.makeUpload(
293+ custom_type=PackageUploadCustomFormat.UEFI)
294+ target_series = self.factory.makeDistroSeries()
295+ target_archive = self.factory.makeArchive(
296+ distribution=target_series.distribution)
297+ copier = CustomUploadsCopier(
298+ target_series, target_archive=target_archive)
299+ copied_upload = copier.copyUpload(original_upload)
300+ self.assertEqual(
301+ PackageUploadStatus.ACCEPTED, copied_upload.packageupload.status)
302+
303+ def test_copyUpload_archive_None_copies_within_archive(self):
304+ # If CustomUploadsCopier was created with no target archive,
305+ # copyUpload copies an upload to the same archive as the original
306+ # upload.
307+ original_upload = self.makeUpload()
308+ target_series = self.factory.makeDistroSeries()
309+ copier = CustomUploadsCopier(target_series)
310+ copied_upload = copier.copyUpload(original_upload)
311+ self.assertEqual(
312+ PackageUploadStatus.ACCEPTED, copied_upload.packageupload.status)
313+ self.assertEqual(
314+ original_upload.packageupload.archive,
315+ copied_upload.packageupload.archive)
316+
317+ def test_copyUpload_to_specified_archive(self):
318+ # If CustomUploadsCopier was created with a target archive,
319+ # copyUpload copies an upload to that archive.
320+ series = self.factory.makeDistroSeries()
321+ original_upload = self.makeUpload(distroseries=series)
322+ archive = self.factory.makeArchive(distribution=series.distribution)
323+ copier = CustomUploadsCopier(series, target_archive=archive)
324+ copied_upload = copier.copyUpload(original_upload)
325+ self.assertEqual(
326+ PackageUploadStatus.ACCEPTED, copied_upload.packageupload.status)
327+ self.assertEqual(archive, copied_upload.packageupload.archive)
328+
329+ def test_copyUpload_from_ppa_to_main_archive(self):
330+ # copyUpload can copy uploads from a PPA to the main archive.
331+ series = self.factory.makeDistroSeries()
332+ archive = self.factory.makeArchive(distribution=series.distribution)
333+ original_upload = self.makeUpload(distroseries=series, archive=archive)
334+ copier = CustomUploadsCopier(
335+ series, target_archive=series.main_archive)
336+ copied_upload = copier.copyUpload(original_upload)
337+ self.assertEqual(
338+ PackageUploadStatus.ACCEPTED, copied_upload.packageupload.status)
339+ self.assertEqual(
340+ series.main_archive, copied_upload.packageupload.archive)
341
342=== modified file 'lib/lp/testing/factory.py'
343--- lib/lp/testing/factory.py 2012-09-20 15:36:33 +0000
344+++ lib/lp/testing/factory.py 2012-09-24 23:55:27 +0000
345@@ -3553,16 +3553,17 @@
346 component=component)
347 return upload
348
349- def makeCustomPackageUpload(self, distroseries=None, pocket=None,
350- custom_type=None, filename=None):
351+ def makeCustomPackageUpload(self, distroseries=None, archive=None,
352+ pocket=None, custom_type=None, filename=None):
353 """Make a `PackageUpload` with a `PackageUploadCustom` attached."""
354 if distroseries is None:
355 distroseries = self.makeDistroSeries()
356+ if archive is None:
357+ archive = distroseries.main_archive
358 if custom_type is None:
359 custom_type = PackageUploadCustomFormat.DEBIAN_INSTALLER
360 upload = self.makePackageUpload(
361- distroseries=distroseries, archive=distroseries.main_archive,
362- pocket=pocket)
363+ distroseries=distroseries, archive=archive, pocket=pocket)
364 file_alias = self.makeLibraryFileAlias(filename=filename)
365 upload.addCustom(file_alias, custom_type)
366 return upload
367
368=== modified file 'lib/lp/testing/tests/test_factory.py'
369--- lib/lp/testing/tests/test_factory.py 2012-07-03 10:29:53 +0000
370+++ lib/lp/testing/tests/test_factory.py 2012-09-24 23:55:27 +0000
371@@ -807,14 +807,16 @@
372
373 def test_makeCustomPackageUpload_passes_on_args(self):
374 distroseries = self.factory.makeDistroSeries()
375+ archive = self.factory.makeArchive()
376 custom_type = PackageUploadCustomFormat.ROSETTA_TRANSLATIONS
377 filename = self.factory.getUniqueString()
378 pu = self.factory.makeCustomPackageUpload(
379- distroseries=distroseries, pocket=PackagePublishingPocket.PROPOSED,
380- custom_type=custom_type, filename=filename)
381+ distroseries=distroseries, archive=archive,
382+ pocket=PackagePublishingPocket.PROPOSED, custom_type=custom_type,
383+ filename=filename)
384 custom = list(pu.customfiles)[0]
385 self.assertEqual(distroseries, pu.distroseries)
386- self.assertEqual(distroseries.distribution, pu.archive.distribution)
387+ self.assertEqual(archive, pu.archive)
388 self.assertEqual(PackagePublishingPocket.PROPOSED, pu.pocket)
389 self.assertEqual(custom_type, custom.customformat)
390 self.assertEqual(filename, custom.libraryfilealias.filename)