Merge lp:~cjwatson/launchpad/refactor-archive-signing into lp:launchpad

Proposed by Colin Watson on 2018-01-19
Status: Merged
Merged at revision: 18584
Proposed branch: lp:~cjwatson/launchpad/refactor-archive-signing
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/archive-signing-run-parts
Diff against target: 583 lines (+274/-30)
11 files modified
lib/lp/archivepublisher/archivesigningkey.py (+6/-4)
lib/lp/archivepublisher/customupload.py (+17/-0)
lib/lp/archivepublisher/debian_installer.py (+5/-1)
lib/lp/archivepublisher/dist_upgrader.py (+6/-2)
lib/lp/archivepublisher/publishing.py (+11/-10)
lib/lp/archivepublisher/signing.py (+5/-5)
lib/lp/archivepublisher/tests/test_customupload.py (+94/-2)
lib/lp/archivepublisher/tests/test_debian_installer.py (+23/-2)
lib/lp/archivepublisher/tests/test_dist_upgrader.py (+23/-2)
lib/lp/archivepublisher/tests/test_publisher.py (+50/-1)
lib/lp/archivepublisher/tests/test_signing.py (+34/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/refactor-archive-signing
Reviewer Review Type Date Requested Status
William Grant code 2018-01-19 Approve on 2018-03-25
Review via email: mp+336377@code.launchpad.net

Commit message

Extend custom uploads and Release file signing to use the new ISignableArchive interface.

Description of the change

This replaces ubuntu-archive-publishing/publish-distro.d/10-sign-releases.

To post a comment you must log in.
18541. By Colin Watson on 2018-01-20

Use RunPartsMixin in a couple more places.

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/archivepublisher/archivesigningkey.py'
2--- lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:52 +0000
3+++ lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:53 +0000
4@@ -119,19 +119,21 @@
5 "No signing key available for %s" %
6 self.archive.displayname)
7
8- def signRepository(self, suite, log=None):
9+ def signRepository(self, suite, suffix='', log=None):
10 """See `ISignableArchive`."""
11 suite_path = os.path.join(self._archive_root_path, 'dists', suite)
12- release_file_path = os.path.join(suite_path, 'Release')
13+ release_file_path = os.path.join(suite_path, 'Release' + suffix)
14 if not os.path.exists(release_file_path):
15 raise AssertionError(
16 "Release file doesn't exist in the repository: %s" %
17 release_file_path)
18
19 self._makeSignatures([
20- (release_file_path, os.path.join(suite_path, 'Release.gpg'),
21+ (release_file_path,
22+ os.path.join(suite_path, 'Release.gpg' + suffix),
23 SigningMode.DETACHED, suite),
24- (release_file_path, os.path.join(suite_path, 'InRelease'),
25+ (release_file_path,
26+ os.path.join(suite_path, 'InRelease' + suffix),
27 SigningMode.CLEAR, suite),
28 ], log=log)
29
30
31=== modified file 'lib/lp/archivepublisher/customupload.py'
32--- lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:52 +0000
33+++ lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:53 +0000
34@@ -26,6 +26,7 @@
35 Version as make_version,
36 VersionError,
37 )
38+from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive
39 from lp.services.librarian.utils import copy_and_close
40 from lp.soyuz.interfaces.queue import (
41 CustomUploadError,
42@@ -268,6 +269,20 @@
43 if not os.path.isdir(parentdir):
44 os.makedirs(parentdir, 0o755)
45
46+ def shouldSign(self, filename):
47+ """Returns True if the given filename should be signed."""
48+ return False
49+
50+ def sign(self, archive, suite, filename):
51+ """Sign a file.
52+
53+ For now, we always write a detached signature to the input file name
54+ plus ".gpg".
55+ """
56+ signable_archive = ISignableArchive(archive)
57+ if signable_archive.can_sign:
58+ signable_archive.signFile(suite, filename, log=self.logger)
59+
60 def installFiles(self, archive, suite):
61 """Install the files from the custom upload to the archive."""
62 assert self.tmpdir is not None, "Must extract tarfile first"
63@@ -316,6 +331,8 @@
64 else:
65 shutil.copy(sourcepath, destpath)
66 os.chmod(destpath, 0o644)
67+ if self.shouldSign(destpath):
68+ self.sign(archive, suite, destpath)
69
70 extracted = True
71
72
73=== modified file 'lib/lp/archivepublisher/debian_installer.py'
74--- lib/lp/archivepublisher/debian_installer.py 2016-06-07 17:07:35 +0000
75+++ lib/lp/archivepublisher/debian_installer.py 2018-01-26 13:42:53 +0000
76@@ -1,4 +1,4 @@
77-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
78+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
79 # GNU Affero General Public License version 3 (see the file LICENSE).
80
81 """The processing of debian installer tarballs."""
82@@ -76,3 +76,7 @@
83
84 def shouldInstall(self, filename):
85 return filename.startswith('%s/' % self.version)
86+
87+ def shouldSign(self, filename):
88+ """Sign checksums files."""
89+ return filename.endswith('SUMS')
90
91=== modified file 'lib/lp/archivepublisher/dist_upgrader.py'
92--- lib/lp/archivepublisher/dist_upgrader.py 2016-06-07 17:07:35 +0000
93+++ lib/lp/archivepublisher/dist_upgrader.py 2018-01-26 13:42:53 +0000
94@@ -1,4 +1,4 @@
95-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
96+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
97 # GNU Affero General Public License version 3 (see the file LICENSE).
98
99 """The processing of dist-upgrader tarballs."""
100@@ -81,7 +81,7 @@
101 return None
102
103 def shouldInstall(self, filename):
104- """ Install files from a dist-upgrader tarball.
105+ """Install files from a dist-upgrader tarball.
106
107 It raises DistUpgraderBadVersion if if finds a directory name that
108 could not be treated as a valid Debian version.
109@@ -100,3 +100,7 @@
110 except BadUpstreamError as exc:
111 raise DistUpgraderBadVersion(self.tarfile_path, exc)
112 return version and not filename.startswith('current')
113+
114+ def shouldSign(self, filename):
115+ """Sign *.tar.gz files."""
116+ return filename.endswith('.tar.gz')
117
118=== modified file 'lib/lp/archivepublisher/publishing.py'
119--- lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:52 +0000
120+++ lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:53 +0000
121@@ -57,9 +57,7 @@
122 build_source_stanza_fields,
123 build_translations_stanza_fields,
124 )
125-from lp.archivepublisher.interfaces.archivesigningkey import (
126- IArchiveSigningKey,
127- )
128+from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive
129 from lp.archivepublisher.model.ftparchive import FTPArchiveHandler
130 from lp.archivepublisher.utils import (
131 get_ppa_reference,
132@@ -1245,20 +1243,23 @@
133 if distroseries.publish_by_hash:
134 self._updateByHash(suite, "Release.new")
135
136- os.rename(
137- os.path.join(suite_dir, "Release.new"),
138- os.path.join(suite_dir, "Release"))
139-
140- if self.archive.signing_key is not None:
141+ signable_archive = ISignableArchive(self.archive)
142+ if signable_archive.can_sign:
143 # Sign the repository.
144 self.log.debug("Signing Release file for %s" % suite)
145- IArchiveSigningKey(self.archive).signRepository(suite)
146+ signable_archive.signRepository(suite, suffix=".new", log=self.log)
147 core_files.add("Release.gpg")
148 core_files.add("InRelease")
149 else:
150- # Skip signature if the archive signing key is undefined.
151+ # Skip signature if the archive is not set up for signing.
152 self.log.debug("No signing key available, skipping signature.")
153
154+ for name in ("Release", "Release.gpg", "InRelease"):
155+ if name in core_files:
156+ os.rename(
157+ os.path.join(suite_dir, "%s.new" % name),
158+ os.path.join(suite_dir, name))
159+
160 # Make sure all the timestamps match, to make it easier to insert
161 # caching headers on mirrors.
162 self._syncTimestamps(suite, core_files)
163
164=== modified file 'lib/lp/archivepublisher/signing.py'
165--- lib/lp/archivepublisher/signing.py 2018-01-26 13:42:52 +0000
166+++ lib/lp/archivepublisher/signing.py 2018-01-26 13:42:53 +0000
167@@ -28,9 +28,6 @@
168
169 from lp.archivepublisher.config import getPubConfig
170 from lp.archivepublisher.customupload import CustomUpload
171-from lp.archivepublisher.interfaces.archivesigningkey import (
172- IArchiveSigningKey,
173- )
174 from lp.services.osutils import remove_if_exists
175 from lp.soyuz.interfaces.queue import CustomUploadError
176
177@@ -379,12 +376,15 @@
178 with DirectoryHash(versiondir, self.temproot) as hasher:
179 hasher.add_dir(versiondir)
180 for checksum_path in hasher.checksum_paths:
181- if archive.signing_key is not None:
182- IArchiveSigningKey(archive).signFile(suite, checksum_path)
183+ if self.shouldSign(checksum_path):
184+ self.sign(archive, suite, checksum_path)
185
186 def shouldInstall(self, filename):
187 return filename.startswith("%s/" % self.version)
188
189+ def shouldSign(self, filename):
190+ return filename.endswith("SUMS")
191+
192
193 class UefiUpload(SigningUpload):
194 """Legacy UEFI Signing custom upload.
195
196=== modified file 'lib/lp/archivepublisher/tests/test_customupload.py'
197--- lib/lp/archivepublisher/tests/test_customupload.py 2012-05-28 13:13:53 +0000
198+++ lib/lp/archivepublisher/tests/test_customupload.py 2018-01-26 13:42:53 +0000
199@@ -1,4 +1,4 @@
200-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
201+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
202 # GNU Affero General Public License version 3 (see the file LICENSE).
203
204 """Tests for `CustomUploads`."""
205@@ -13,13 +13,40 @@
206 import tempfile
207 import unittest
208
209+from fixtures import MonkeyPatch
210+from testtools.deferredruntest import AsynchronousDeferredRunTest
211+from testtools.matchers import (
212+ Equals,
213+ MatchesDict,
214+ Not,
215+ PathExists,
216+ )
217+from twisted.internet import defer
218+from zope.component import getUtility
219+
220+from lp.archivepublisher.config import getPubConfig
221 from lp.archivepublisher.customupload import (
222 CustomUpload,
223 CustomUploadTarballBadFile,
224 CustomUploadTarballBadSymLink,
225 CustomUploadTarballInvalidFileType,
226 )
227-from lp.testing import TestCase
228+from lp.archivepublisher.interfaces.archivesigningkey import (
229+ IArchiveSigningKey,
230+ )
231+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
232+from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
233+from lp.services.gpg.interfaces import IGPGHandler
234+from lp.services.osutils import write_file
235+from lp.soyuz.enums import ArchivePurpose
236+from lp.testing import (
237+ TestCase,
238+ TestCaseWithFactory,
239+ )
240+from lp.testing.fakemethod import FakeMethod
241+from lp.testing.gpgkeys import gpgkeysdir
242+from lp.testing.keyserver import InProcessKeyServerFixture
243+from lp.testing.layers import LaunchpadZopelessLayer
244
245
246 class TestCustomUpload(unittest.TestCase):
247@@ -198,3 +225,68 @@
248 self.custom_processor.extract)
249 finally:
250 shutil.rmtree(self.tarfile_path)
251+
252+
253+class TestSigning(TestCaseWithFactory, RunPartsMixin):
254+
255+ layer = LaunchpadZopelessLayer
256+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
257+
258+ def setUp(self):
259+ super(TestSigning, self).setUp()
260+ self.temp_dir = self.makeTemporaryDirectory()
261+ self.distro = self.factory.makeDistribution()
262+ db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
263+ self.distro)
264+ db_pubconf.root_dir = unicode(self.temp_dir)
265+ self.archive = self.factory.makeArchive(
266+ distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
267+
268+ def test_sign_without_signing_key(self):
269+ filename = os.path.join(
270+ getPubConfig(self.archive).archiveroot, "file")
271+ self.assertIsNone(self.archive.signing_key)
272+ custom_processor = CustomUpload()
273+ custom_processor.sign(self.archive, "suite", filename)
274+ self.assertThat("%s.gpg" % filename, Not(PathExists()))
275+
276+ @defer.inlineCallbacks
277+ def test_sign_with_signing_key(self):
278+ filename = os.path.join(
279+ getPubConfig(self.archive).archiveroot, "file")
280+ write_file(filename, "contents")
281+ self.assertIsNone(self.archive.signing_key)
282+ self.useFixture(InProcessKeyServerFixture()).start()
283+ key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
284+ yield IArchiveSigningKey(self.archive).setSigningKey(
285+ key_path, async_keyserver=True)
286+ self.assertIsNotNone(self.archive.signing_key)
287+ custom_processor = CustomUpload()
288+ custom_processor.sign(self.archive, "suite", filename)
289+ with open(filename) as cleartext_file:
290+ cleartext = cleartext_file.read()
291+ with open("%s.gpg" % filename) as signature_file:
292+ signature = getUtility(IGPGHandler).getVerifiedSignature(
293+ cleartext, signature_file.read())
294+ self.assertEqual(
295+ self.archive.signing_key.fingerprint, signature.fingerprint)
296+
297+ def test_sign_with_external_run_parts(self):
298+ self.enableRunParts(distribution_name=self.distro.name)
299+ filename = os.path.join(
300+ getPubConfig(self.archive).archiveroot, "file")
301+ write_file(filename, "contents")
302+ self.assertIsNone(self.archive.signing_key)
303+ run_parts_fixture = self.useFixture(MonkeyPatch(
304+ "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod()))
305+ custom_processor = CustomUpload()
306+ custom_processor.sign(self.archive, "suite", filename)
307+ args, kwargs = run_parts_fixture.new_value.calls[0]
308+ self.assertEqual((self.distro.name, "sign.d"), args)
309+ self.assertThat(kwargs["env"], MatchesDict({
310+ "INPUT_PATH": Equals(filename),
311+ "OUTPUT_PATH": Equals("%s.gpg" % filename),
312+ "MODE": Equals("detached"),
313+ "DISTRIBUTION": Equals(self.distro.name),
314+ "SUITE": Equals("suite"),
315+ }))
316
317=== modified file 'lib/lp/archivepublisher/tests/test_debian_installer.py'
318--- lib/lp/archivepublisher/tests/test_debian_installer.py 2016-06-07 17:07:35 +0000
319+++ lib/lp/archivepublisher/tests/test_debian_installer.py 2018-01-26 13:42:53 +0000
320@@ -1,4 +1,4 @@
321-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
322+# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
323 # GNU Affero General Public License version 3 (see the file LICENSE).
324
325 """Test debian-installer custom uploads.
326@@ -8,7 +8,9 @@
327 """
328
329 import os
330+from textwrap import dedent
331
332+from testtools.matchers import DirContains
333 from zope.component import getUtility
334
335 from lp.archivepublisher.config import getPubConfig
336@@ -18,13 +20,14 @@
337 )
338 from lp.archivepublisher.debian_installer import DebianInstallerUpload
339 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
340+from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
341 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
342 from lp.soyuz.enums import ArchivePurpose
343 from lp.testing import TestCaseWithFactory
344 from lp.testing.layers import ZopelessDatabaseLayer
345
346
347-class TestDebianInstaller(TestCaseWithFactory):
348+class TestDebianInstaller(RunPartsMixin, TestCaseWithFactory):
349
350 layer = ZopelessDatabaseLayer
351
352@@ -161,6 +164,24 @@
353 self.assertEqual(
354 0o755, os.stat(self.getInstallerPath(directory)).st_mode & 0o777)
355
356+ def test_sign_with_external_run_parts(self):
357+ self.enableRunParts(distribution_name=self.distro.name)
358+ with open(os.path.join(
359+ self.parts_directory, self.distro.name, "sign.d",
360+ "10-sign"), "w") as f:
361+ f.write(dedent("""\
362+ #! /bin/sh
363+ touch "$OUTPUT_PATH"
364+ """))
365+ os.fchmod(f.fileno(), 0o755)
366+ self.openArchive()
367+ self.addFile("images/list", "a list")
368+ self.addFile("images/SHA256SUMS", "a checksum")
369+ self.process()
370+ self.assertThat(
371+ self.getInstallerPath("images"),
372+ DirContains(["list", "SHA256SUMS", "SHA256SUMS.gpg"]))
373+
374 def test_getSeriesKey_extracts_architecture(self):
375 # getSeriesKey extracts the architecture from an upload's filename.
376 self.openArchive()
377
378=== modified file 'lib/lp/archivepublisher/tests/test_dist_upgrader.py'
379--- lib/lp/archivepublisher/tests/test_dist_upgrader.py 2016-06-07 17:07:35 +0000
380+++ lib/lp/archivepublisher/tests/test_dist_upgrader.py 2018-01-26 13:42:53 +0000
381@@ -1,4 +1,4 @@
382-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
383+# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
384 # GNU Affero General Public License version 3 (see the file LICENSE).
385
386 """Test dist-upgrader custom uploads.
387@@ -8,7 +8,9 @@
388 """
389
390 import os
391+from textwrap import dedent
392
393+from testtools.matchers import DirContains
394 from zope.component import getUtility
395
396 from lp.archivepublisher.config import getPubConfig
397@@ -21,6 +23,7 @@
398 DistUpgraderUpload,
399 )
400 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
401+from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
402 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
403 from lp.soyuz.enums import ArchivePurpose
404 from lp.testing import TestCaseWithFactory
405@@ -33,7 +36,7 @@
406 self.archiveroot = archiveroot
407
408
409-class TestDistUpgrader(TestCaseWithFactory):
410+class TestDistUpgrader(RunPartsMixin, TestCaseWithFactory):
411
412 layer = ZopelessDatabaseLayer
413
414@@ -109,6 +112,24 @@
415 self.tarfile.add_file("foobar/foobar/dapper.tar.gz", "")
416 self.assertRaises(DistUpgraderBadVersion, self.process)
417
418+ def test_sign_with_external_run_parts(self):
419+ self.enableRunParts(distribution_name=self.distro.name)
420+ with open(os.path.join(
421+ self.parts_directory, self.distro.name, "sign.d",
422+ "10-sign"), "w") as f:
423+ f.write(dedent("""\
424+ #! /bin/sh
425+ touch "$OUTPUT_PATH"
426+ """))
427+ os.fchmod(f.fileno(), 0o755)
428+ self.openArchive("20060302.0120")
429+ self.tarfile.add_file("20060302.0120/list", "a list")
430+ self.tarfile.add_file("20060302.0120/foo.tar.gz", "a tarball")
431+ self.process()
432+ self.assertThat(
433+ os.path.join(self.getUpgraderPath(), "20060302.0120"),
434+ DirContains(["list", "foo.tar.gz", "foo.tar.gz.gpg"]))
435+
436 def test_getSeriesKey_extracts_architecture(self):
437 # getSeriesKey extracts the architecture from an upload's filename.
438 self.openArchive("20060302.0120")
439
440=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
441--- lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:52 +0000
442+++ lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:53 +0000
443@@ -67,6 +67,7 @@
444 I18nIndex,
445 Publisher,
446 )
447+from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
448 from lp.archivepublisher.utils import RepositoryIndexFile
449 from lp.registry.interfaces.distribution import IDistributionSet
450 from lp.registry.interfaces.distroseries import IDistroSeries
451@@ -2925,7 +2926,7 @@
452 os.rename(temporary_dists, original_dists)
453
454
455-class TestPublisherRepositorySignatures(TestPublisherBase):
456+class TestPublisherRepositorySignatures(RunPartsMixin, TestPublisherBase):
457 """Testing `Publisher` signature behaviour."""
458
459 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
460@@ -3065,6 +3066,54 @@
461 self.assertThat(
462 sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
463
464+ def testRepositorySignatureWithExternalRunParts(self):
465+ """Check publisher behaviour when signing repositories.
466+
467+ When a 'sign.d' run-parts directory is configured for the archive,
468+ it is used to sign the Release file.
469+ """
470+ cprov = getUtility(IPersonSet).getByName('cprov')
471+ self.assertIsNone(cprov.archive.signing_key)
472+ self.enableRunParts(distribution_name=cprov.archive.distribution.name)
473+ sign_directory = os.path.join(
474+ self.parts_directory, cprov.archive.distribution.name, 'sign.d')
475+ with open(os.path.join(sign_directory, '10-sign'), 'w') as sign_script:
476+ sign_script.write(dedent("""\
477+ #! /bin/sh
478+ echo "$MODE signature of $INPUT_PATH ($DISTRIBUTION/$SUITE)" \\
479+ >"$OUTPUT_PATH"
480+ """))
481+ os.fchmod(sign_script.fileno(), 0o755)
482+
483+ self.setupPublisher(cprov.archive)
484+ self.archive_publisher._syncTimestamps = FakeMethod()
485+
486+ self._publishArchive(cprov.archive)
487+
488+ # Release exists.
489+ self.assertThat(self.release_file_path, PathExists())
490+
491+ # Release.gpg and InRelease exist with suitable fake signatures.
492+ # Note that the signatures are made before Release.new is renamed to
493+ # to Release.
494+ self.assertThat(
495+ self.release_file_signature_path,
496+ FileContains(
497+ "detached signature of %s.new (%s/breezy-autotest)\n" %
498+ (self.release_file_path, cprov.archive.distribution.name)))
499+ self.assertThat(
500+ self.inline_release_file_path,
501+ FileContains(
502+ "clear signature of %s.new (%s/breezy-autotest)\n" %
503+ (self.release_file_path, cprov.archive.distribution.name)))
504+
505+ # The publisher synchronises the various Release file timestamps.
506+ self.assertEqual(1, self.archive_publisher._syncTimestamps.call_count)
507+ sync_args = self.archive_publisher._syncTimestamps.extract_args()[0]
508+ self.assertEqual(self.distroseries.name, sync_args[0])
509+ self.assertThat(
510+ sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
511+
512
513 class TestPublisherLite(TestCaseWithFactory):
514 """Lightweight unit tests for the publisher."""
515
516=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
517--- lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:52 +0000
518+++ lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:53 +0000
519@@ -13,9 +13,11 @@
520 from testtools.deferredruntest import AsynchronousDeferredRunTest
521 from testtools.matchers import (
522 Contains,
523+ Equals,
524 FileContains,
525 Matcher,
526 MatchesAll,
527+ MatchesDict,
528 Mismatch,
529 Not,
530 StartsWith,
531@@ -36,6 +38,7 @@
532 SigningUpload,
533 UefiUpload,
534 )
535+from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
536 from lp.services.osutils import write_file
537 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
538 from lp.soyuz.enums import ArchivePurpose
539@@ -215,7 +218,7 @@
540 return os.path.join(pubconf.archiveroot, "dists", self.suite, "main")
541
542
543-class TestSigning(TestSigningHelpers):
544+class TestSigning(RunPartsMixin, TestSigningHelpers):
545
546 def getSignedPath(self, loader_type, arch):
547 return os.path.join(self.getDistsPath(), "signed",
548@@ -934,6 +937,36 @@
549 "1.0/signed.tar.gz",
550 ]]))
551
552+ def test_checksumming_tree_signed_with_external_run_parts(self):
553+ # Checksum files can be signed using an external run-parts helper.
554+ # We disable subprocess.call because there's just too much going on,
555+ # so we can't test this completely, but we can at least test that
556+ # run_parts is called.
557+ self.enableRunParts(distribution_name=self.distro.name)
558+ run_parts_fixture = self.useFixture(MonkeyPatch(
559+ "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod()))
560+ self.setUpUefiKeys()
561+ self.setUpKmodKeys()
562+ self.setUpOpalKeys()
563+ self.openArchive("test", "1.0", "amd64")
564+ self.tarfile.add_file("1.0/empty.efi", "")
565+ self.tarfile.add_file("1.0/empty.ko", "")
566+ self.tarfile.add_file("1.0/empty.opal", "")
567+ self.process_emulate()
568+ sha256file = os.path.join(self.getSignedPath("test", "amd64"),
569+ "1.0", "SHA256SUMS")
570+ self.assertTrue(os.path.exists(sha256file))
571+ self.assertEqual(1, run_parts_fixture.new_value.call_count)
572+ args, kwargs = run_parts_fixture.new_value.calls[-1]
573+ self.assertEqual((self.distro.name, "sign.d"), args)
574+ self.assertThat(kwargs["env"], MatchesDict({
575+ "INPUT_PATH": Equals(sha256file),
576+ "OUTPUT_PATH": Equals("%s.gpg" % sha256file),
577+ "MODE": Equals("detached"),
578+ "DISTRIBUTION": Equals(self.distro.name),
579+ "SUITE": Equals(self.suite),
580+ }))
581+
582
583 class TestUefi(TestSigningHelpers):
584