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

Proposed by Colin Watson
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 Approve
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.
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
=== modified file 'lib/lp/archivepublisher/archivesigningkey.py'
--- lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:53 +0000
@@ -119,19 +119,21 @@
119 "No signing key available for %s" %119 "No signing key available for %s" %
120 self.archive.displayname)120 self.archive.displayname)
121121
122 def signRepository(self, suite, log=None):122 def signRepository(self, suite, suffix='', log=None):
123 """See `ISignableArchive`."""123 """See `ISignableArchive`."""
124 suite_path = os.path.join(self._archive_root_path, 'dists', suite)124 suite_path = os.path.join(self._archive_root_path, 'dists', suite)
125 release_file_path = os.path.join(suite_path, 'Release')125 release_file_path = os.path.join(suite_path, 'Release' + suffix)
126 if not os.path.exists(release_file_path):126 if not os.path.exists(release_file_path):
127 raise AssertionError(127 raise AssertionError(
128 "Release file doesn't exist in the repository: %s" %128 "Release file doesn't exist in the repository: %s" %
129 release_file_path)129 release_file_path)
130130
131 self._makeSignatures([131 self._makeSignatures([
132 (release_file_path, os.path.join(suite_path, 'Release.gpg'),132 (release_file_path,
133 os.path.join(suite_path, 'Release.gpg' + suffix),
133 SigningMode.DETACHED, suite),134 SigningMode.DETACHED, suite),
134 (release_file_path, os.path.join(suite_path, 'InRelease'),135 (release_file_path,
136 os.path.join(suite_path, 'InRelease' + suffix),
135 SigningMode.CLEAR, suite),137 SigningMode.CLEAR, suite),
136 ], log=log)138 ], log=log)
137139
138140
=== modified file 'lib/lp/archivepublisher/customupload.py'
--- lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:53 +0000
@@ -26,6 +26,7 @@
26 Version as make_version,26 Version as make_version,
27 VersionError,27 VersionError,
28 )28 )
29from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive
29from lp.services.librarian.utils import copy_and_close30from lp.services.librarian.utils import copy_and_close
30from lp.soyuz.interfaces.queue import (31from lp.soyuz.interfaces.queue import (
31 CustomUploadError,32 CustomUploadError,
@@ -268,6 +269,20 @@
268 if not os.path.isdir(parentdir):269 if not os.path.isdir(parentdir):
269 os.makedirs(parentdir, 0o755)270 os.makedirs(parentdir, 0o755)
270271
272 def shouldSign(self, filename):
273 """Returns True if the given filename should be signed."""
274 return False
275
276 def sign(self, archive, suite, filename):
277 """Sign a file.
278
279 For now, we always write a detached signature to the input file name
280 plus ".gpg".
281 """
282 signable_archive = ISignableArchive(archive)
283 if signable_archive.can_sign:
284 signable_archive.signFile(suite, filename, log=self.logger)
285
271 def installFiles(self, archive, suite):286 def installFiles(self, archive, suite):
272 """Install the files from the custom upload to the archive."""287 """Install the files from the custom upload to the archive."""
273 assert self.tmpdir is not None, "Must extract tarfile first"288 assert self.tmpdir is not None, "Must extract tarfile first"
@@ -316,6 +331,8 @@
316 else:331 else:
317 shutil.copy(sourcepath, destpath)332 shutil.copy(sourcepath, destpath)
318 os.chmod(destpath, 0o644)333 os.chmod(destpath, 0o644)
334 if self.shouldSign(destpath):
335 self.sign(archive, suite, destpath)
319336
320 extracted = True337 extracted = True
321338
322339
=== modified file 'lib/lp/archivepublisher/debian_installer.py'
--- lib/lp/archivepublisher/debian_installer.py 2016-06-07 17:07:35 +0000
+++ lib/lp/archivepublisher/debian_installer.py 2018-01-26 13:42:53 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""The processing of debian installer tarballs."""4"""The processing of debian installer tarballs."""
@@ -76,3 +76,7 @@
7676
77 def shouldInstall(self, filename):77 def shouldInstall(self, filename):
78 return filename.startswith('%s/' % self.version)78 return filename.startswith('%s/' % self.version)
79
80 def shouldSign(self, filename):
81 """Sign checksums files."""
82 return filename.endswith('SUMS')
7983
=== modified file 'lib/lp/archivepublisher/dist_upgrader.py'
--- lib/lp/archivepublisher/dist_upgrader.py 2016-06-07 17:07:35 +0000
+++ lib/lp/archivepublisher/dist_upgrader.py 2018-01-26 13:42:53 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""The processing of dist-upgrader tarballs."""4"""The processing of dist-upgrader tarballs."""
@@ -81,7 +81,7 @@
81 return None81 return None
8282
83 def shouldInstall(self, filename):83 def shouldInstall(self, filename):
84 """ Install files from a dist-upgrader tarball.84 """Install files from a dist-upgrader tarball.
8585
86 It raises DistUpgraderBadVersion if if finds a directory name that86 It raises DistUpgraderBadVersion if if finds a directory name that
87 could not be treated as a valid Debian version.87 could not be treated as a valid Debian version.
@@ -100,3 +100,7 @@
100 except BadUpstreamError as exc:100 except BadUpstreamError as exc:
101 raise DistUpgraderBadVersion(self.tarfile_path, exc)101 raise DistUpgraderBadVersion(self.tarfile_path, exc)
102 return version and not filename.startswith('current')102 return version and not filename.startswith('current')
103
104 def shouldSign(self, filename):
105 """Sign *.tar.gz files."""
106 return filename.endswith('.tar.gz')
103107
=== modified file 'lib/lp/archivepublisher/publishing.py'
--- lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:53 +0000
@@ -57,9 +57,7 @@
57 build_source_stanza_fields,57 build_source_stanza_fields,
58 build_translations_stanza_fields,58 build_translations_stanza_fields,
59 )59 )
60from lp.archivepublisher.interfaces.archivesigningkey import (60from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive
61 IArchiveSigningKey,
62 )
63from lp.archivepublisher.model.ftparchive import FTPArchiveHandler61from lp.archivepublisher.model.ftparchive import FTPArchiveHandler
64from lp.archivepublisher.utils import (62from lp.archivepublisher.utils import (
65 get_ppa_reference,63 get_ppa_reference,
@@ -1245,20 +1243,23 @@
1245 if distroseries.publish_by_hash:1243 if distroseries.publish_by_hash:
1246 self._updateByHash(suite, "Release.new")1244 self._updateByHash(suite, "Release.new")
12471245
1248 os.rename(1246 signable_archive = ISignableArchive(self.archive)
1249 os.path.join(suite_dir, "Release.new"),1247 if signable_archive.can_sign:
1250 os.path.join(suite_dir, "Release"))
1251
1252 if self.archive.signing_key is not None:
1253 # Sign the repository.1248 # Sign the repository.
1254 self.log.debug("Signing Release file for %s" % suite)1249 self.log.debug("Signing Release file for %s" % suite)
1255 IArchiveSigningKey(self.archive).signRepository(suite)1250 signable_archive.signRepository(suite, suffix=".new", log=self.log)
1256 core_files.add("Release.gpg")1251 core_files.add("Release.gpg")
1257 core_files.add("InRelease")1252 core_files.add("InRelease")
1258 else:1253 else:
1259 # Skip signature if the archive signing key is undefined.1254 # Skip signature if the archive is not set up for signing.
1260 self.log.debug("No signing key available, skipping signature.")1255 self.log.debug("No signing key available, skipping signature.")
12611256
1257 for name in ("Release", "Release.gpg", "InRelease"):
1258 if name in core_files:
1259 os.rename(
1260 os.path.join(suite_dir, "%s.new" % name),
1261 os.path.join(suite_dir, name))
1262
1262 # Make sure all the timestamps match, to make it easier to insert1263 # Make sure all the timestamps match, to make it easier to insert
1263 # caching headers on mirrors.1264 # caching headers on mirrors.
1264 self._syncTimestamps(suite, core_files)1265 self._syncTimestamps(suite, core_files)
12651266
=== modified file 'lib/lp/archivepublisher/signing.py'
--- lib/lp/archivepublisher/signing.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/signing.py 2018-01-26 13:42:53 +0000
@@ -28,9 +28,6 @@
2828
29from lp.archivepublisher.config import getPubConfig29from lp.archivepublisher.config import getPubConfig
30from lp.archivepublisher.customupload import CustomUpload30from lp.archivepublisher.customupload import CustomUpload
31from lp.archivepublisher.interfaces.archivesigningkey import (
32 IArchiveSigningKey,
33 )
34from lp.services.osutils import remove_if_exists31from lp.services.osutils import remove_if_exists
35from lp.soyuz.interfaces.queue import CustomUploadError32from lp.soyuz.interfaces.queue import CustomUploadError
3633
@@ -379,12 +376,15 @@
379 with DirectoryHash(versiondir, self.temproot) as hasher:376 with DirectoryHash(versiondir, self.temproot) as hasher:
380 hasher.add_dir(versiondir)377 hasher.add_dir(versiondir)
381 for checksum_path in hasher.checksum_paths:378 for checksum_path in hasher.checksum_paths:
382 if archive.signing_key is not None:379 if self.shouldSign(checksum_path):
383 IArchiveSigningKey(archive).signFile(suite, checksum_path)380 self.sign(archive, suite, checksum_path)
384381
385 def shouldInstall(self, filename):382 def shouldInstall(self, filename):
386 return filename.startswith("%s/" % self.version)383 return filename.startswith("%s/" % self.version)
387384
385 def shouldSign(self, filename):
386 return filename.endswith("SUMS")
387
388388
389class UefiUpload(SigningUpload):389class UefiUpload(SigningUpload):
390 """Legacy UEFI Signing custom upload.390 """Legacy UEFI Signing custom upload.
391391
=== modified file 'lib/lp/archivepublisher/tests/test_customupload.py'
--- lib/lp/archivepublisher/tests/test_customupload.py 2012-05-28 13:13:53 +0000
+++ lib/lp/archivepublisher/tests/test_customupload.py 2018-01-26 13:42:53 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for `CustomUploads`."""4"""Tests for `CustomUploads`."""
@@ -13,13 +13,40 @@
13import tempfile13import tempfile
14import unittest14import unittest
1515
16from fixtures import MonkeyPatch
17from testtools.deferredruntest import AsynchronousDeferredRunTest
18from testtools.matchers import (
19 Equals,
20 MatchesDict,
21 Not,
22 PathExists,
23 )
24from twisted.internet import defer
25from zope.component import getUtility
26
27from lp.archivepublisher.config import getPubConfig
16from lp.archivepublisher.customupload import (28from lp.archivepublisher.customupload import (
17 CustomUpload,29 CustomUpload,
18 CustomUploadTarballBadFile,30 CustomUploadTarballBadFile,
19 CustomUploadTarballBadSymLink,31 CustomUploadTarballBadSymLink,
20 CustomUploadTarballInvalidFileType,32 CustomUploadTarballInvalidFileType,
21 )33 )
22from lp.testing import TestCase34from lp.archivepublisher.interfaces.archivesigningkey import (
35 IArchiveSigningKey,
36 )
37from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
38from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
39from lp.services.gpg.interfaces import IGPGHandler
40from lp.services.osutils import write_file
41from lp.soyuz.enums import ArchivePurpose
42from lp.testing import (
43 TestCase,
44 TestCaseWithFactory,
45 )
46from lp.testing.fakemethod import FakeMethod
47from lp.testing.gpgkeys import gpgkeysdir
48from lp.testing.keyserver import InProcessKeyServerFixture
49from lp.testing.layers import LaunchpadZopelessLayer
2350
2451
25class TestCustomUpload(unittest.TestCase):52class TestCustomUpload(unittest.TestCase):
@@ -198,3 +225,68 @@
198 self.custom_processor.extract)225 self.custom_processor.extract)
199 finally:226 finally:
200 shutil.rmtree(self.tarfile_path)227 shutil.rmtree(self.tarfile_path)
228
229
230class TestSigning(TestCaseWithFactory, RunPartsMixin):
231
232 layer = LaunchpadZopelessLayer
233 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
234
235 def setUp(self):
236 super(TestSigning, self).setUp()
237 self.temp_dir = self.makeTemporaryDirectory()
238 self.distro = self.factory.makeDistribution()
239 db_pubconf = getUtility(IPublisherConfigSet).getByDistribution(
240 self.distro)
241 db_pubconf.root_dir = unicode(self.temp_dir)
242 self.archive = self.factory.makeArchive(
243 distribution=self.distro, purpose=ArchivePurpose.PRIMARY)
244
245 def test_sign_without_signing_key(self):
246 filename = os.path.join(
247 getPubConfig(self.archive).archiveroot, "file")
248 self.assertIsNone(self.archive.signing_key)
249 custom_processor = CustomUpload()
250 custom_processor.sign(self.archive, "suite", filename)
251 self.assertThat("%s.gpg" % filename, Not(PathExists()))
252
253 @defer.inlineCallbacks
254 def test_sign_with_signing_key(self):
255 filename = os.path.join(
256 getPubConfig(self.archive).archiveroot, "file")
257 write_file(filename, "contents")
258 self.assertIsNone(self.archive.signing_key)
259 self.useFixture(InProcessKeyServerFixture()).start()
260 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
261 yield IArchiveSigningKey(self.archive).setSigningKey(
262 key_path, async_keyserver=True)
263 self.assertIsNotNone(self.archive.signing_key)
264 custom_processor = CustomUpload()
265 custom_processor.sign(self.archive, "suite", filename)
266 with open(filename) as cleartext_file:
267 cleartext = cleartext_file.read()
268 with open("%s.gpg" % filename) as signature_file:
269 signature = getUtility(IGPGHandler).getVerifiedSignature(
270 cleartext, signature_file.read())
271 self.assertEqual(
272 self.archive.signing_key.fingerprint, signature.fingerprint)
273
274 def test_sign_with_external_run_parts(self):
275 self.enableRunParts(distribution_name=self.distro.name)
276 filename = os.path.join(
277 getPubConfig(self.archive).archiveroot, "file")
278 write_file(filename, "contents")
279 self.assertIsNone(self.archive.signing_key)
280 run_parts_fixture = self.useFixture(MonkeyPatch(
281 "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod()))
282 custom_processor = CustomUpload()
283 custom_processor.sign(self.archive, "suite", filename)
284 args, kwargs = run_parts_fixture.new_value.calls[0]
285 self.assertEqual((self.distro.name, "sign.d"), args)
286 self.assertThat(kwargs["env"], MatchesDict({
287 "INPUT_PATH": Equals(filename),
288 "OUTPUT_PATH": Equals("%s.gpg" % filename),
289 "MODE": Equals("detached"),
290 "DISTRIBUTION": Equals(self.distro.name),
291 "SUITE": Equals("suite"),
292 }))
201293
=== modified file 'lib/lp/archivepublisher/tests/test_debian_installer.py'
--- lib/lp/archivepublisher/tests/test_debian_installer.py 2016-06-07 17:07:35 +0000
+++ lib/lp/archivepublisher/tests/test_debian_installer.py 2018-01-26 13:42:53 +0000
@@ -1,4 +1,4 @@
1# Copyright 2012-2016 Canonical Ltd. This software is licensed under the1# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test debian-installer custom uploads.4"""Test debian-installer custom uploads.
@@ -8,7 +8,9 @@
8"""8"""
99
10import os10import os
11from textwrap import dedent
1112
13from testtools.matchers import DirContains
12from zope.component import getUtility14from zope.component import getUtility
1315
14from lp.archivepublisher.config import getPubConfig16from lp.archivepublisher.config import getPubConfig
@@ -18,13 +20,14 @@
18 )20 )
19from lp.archivepublisher.debian_installer import DebianInstallerUpload21from lp.archivepublisher.debian_installer import DebianInstallerUpload
20from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet22from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
23from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
21from lp.services.tarfile_helpers import LaunchpadWriteTarFile24from lp.services.tarfile_helpers import LaunchpadWriteTarFile
22from lp.soyuz.enums import ArchivePurpose25from lp.soyuz.enums import ArchivePurpose
23from lp.testing import TestCaseWithFactory26from lp.testing import TestCaseWithFactory
24from lp.testing.layers import ZopelessDatabaseLayer27from lp.testing.layers import ZopelessDatabaseLayer
2528
2629
27class TestDebianInstaller(TestCaseWithFactory):30class TestDebianInstaller(RunPartsMixin, TestCaseWithFactory):
2831
29 layer = ZopelessDatabaseLayer32 layer = ZopelessDatabaseLayer
3033
@@ -161,6 +164,24 @@
161 self.assertEqual(164 self.assertEqual(
162 0o755, os.stat(self.getInstallerPath(directory)).st_mode & 0o777)165 0o755, os.stat(self.getInstallerPath(directory)).st_mode & 0o777)
163166
167 def test_sign_with_external_run_parts(self):
168 self.enableRunParts(distribution_name=self.distro.name)
169 with open(os.path.join(
170 self.parts_directory, self.distro.name, "sign.d",
171 "10-sign"), "w") as f:
172 f.write(dedent("""\
173 #! /bin/sh
174 touch "$OUTPUT_PATH"
175 """))
176 os.fchmod(f.fileno(), 0o755)
177 self.openArchive()
178 self.addFile("images/list", "a list")
179 self.addFile("images/SHA256SUMS", "a checksum")
180 self.process()
181 self.assertThat(
182 self.getInstallerPath("images"),
183 DirContains(["list", "SHA256SUMS", "SHA256SUMS.gpg"]))
184
164 def test_getSeriesKey_extracts_architecture(self):185 def test_getSeriesKey_extracts_architecture(self):
165 # getSeriesKey extracts the architecture from an upload's filename.186 # getSeriesKey extracts the architecture from an upload's filename.
166 self.openArchive()187 self.openArchive()
167188
=== modified file 'lib/lp/archivepublisher/tests/test_dist_upgrader.py'
--- lib/lp/archivepublisher/tests/test_dist_upgrader.py 2016-06-07 17:07:35 +0000
+++ lib/lp/archivepublisher/tests/test_dist_upgrader.py 2018-01-26 13:42:53 +0000
@@ -1,4 +1,4 @@
1# Copyright 2012-2016 Canonical Ltd. This software is licensed under the1# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test dist-upgrader custom uploads.4"""Test dist-upgrader custom uploads.
@@ -8,7 +8,9 @@
8"""8"""
99
10import os10import os
11from textwrap import dedent
1112
13from testtools.matchers import DirContains
12from zope.component import getUtility14from zope.component import getUtility
1315
14from lp.archivepublisher.config import getPubConfig16from lp.archivepublisher.config import getPubConfig
@@ -21,6 +23,7 @@
21 DistUpgraderUpload,23 DistUpgraderUpload,
22 )24 )
23from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet25from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
26from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
24from lp.services.tarfile_helpers import LaunchpadWriteTarFile27from lp.services.tarfile_helpers import LaunchpadWriteTarFile
25from lp.soyuz.enums import ArchivePurpose28from lp.soyuz.enums import ArchivePurpose
26from lp.testing import TestCaseWithFactory29from lp.testing import TestCaseWithFactory
@@ -33,7 +36,7 @@
33 self.archiveroot = archiveroot36 self.archiveroot = archiveroot
3437
3538
36class TestDistUpgrader(TestCaseWithFactory):39class TestDistUpgrader(RunPartsMixin, TestCaseWithFactory):
3740
38 layer = ZopelessDatabaseLayer41 layer = ZopelessDatabaseLayer
3942
@@ -109,6 +112,24 @@
109 self.tarfile.add_file("foobar/foobar/dapper.tar.gz", "")112 self.tarfile.add_file("foobar/foobar/dapper.tar.gz", "")
110 self.assertRaises(DistUpgraderBadVersion, self.process)113 self.assertRaises(DistUpgraderBadVersion, self.process)
111114
115 def test_sign_with_external_run_parts(self):
116 self.enableRunParts(distribution_name=self.distro.name)
117 with open(os.path.join(
118 self.parts_directory, self.distro.name, "sign.d",
119 "10-sign"), "w") as f:
120 f.write(dedent("""\
121 #! /bin/sh
122 touch "$OUTPUT_PATH"
123 """))
124 os.fchmod(f.fileno(), 0o755)
125 self.openArchive("20060302.0120")
126 self.tarfile.add_file("20060302.0120/list", "a list")
127 self.tarfile.add_file("20060302.0120/foo.tar.gz", "a tarball")
128 self.process()
129 self.assertThat(
130 os.path.join(self.getUpgraderPath(), "20060302.0120"),
131 DirContains(["list", "foo.tar.gz", "foo.tar.gz.gpg"]))
132
112 def test_getSeriesKey_extracts_architecture(self):133 def test_getSeriesKey_extracts_architecture(self):
113 # getSeriesKey extracts the architecture from an upload's filename.134 # getSeriesKey extracts the architecture from an upload's filename.
114 self.openArchive("20060302.0120")135 self.openArchive("20060302.0120")
115136
=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
--- lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:53 +0000
@@ -67,6 +67,7 @@
67 I18nIndex,67 I18nIndex,
68 Publisher,68 Publisher,
69 )69 )
70from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
70from lp.archivepublisher.utils import RepositoryIndexFile71from lp.archivepublisher.utils import RepositoryIndexFile
71from lp.registry.interfaces.distribution import IDistributionSet72from lp.registry.interfaces.distribution import IDistributionSet
72from lp.registry.interfaces.distroseries import IDistroSeries73from lp.registry.interfaces.distroseries import IDistroSeries
@@ -2925,7 +2926,7 @@
2925 os.rename(temporary_dists, original_dists)2926 os.rename(temporary_dists, original_dists)
29262927
29272928
2928class TestPublisherRepositorySignatures(TestPublisherBase):2929class TestPublisherRepositorySignatures(RunPartsMixin, TestPublisherBase):
2929 """Testing `Publisher` signature behaviour."""2930 """Testing `Publisher` signature behaviour."""
29302931
2931 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)2932 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
@@ -3065,6 +3066,54 @@
3065 self.assertThat(3066 self.assertThat(
3066 sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))3067 sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
30673068
3069 def testRepositorySignatureWithExternalRunParts(self):
3070 """Check publisher behaviour when signing repositories.
3071
3072 When a 'sign.d' run-parts directory is configured for the archive,
3073 it is used to sign the Release file.
3074 """
3075 cprov = getUtility(IPersonSet).getByName('cprov')
3076 self.assertIsNone(cprov.archive.signing_key)
3077 self.enableRunParts(distribution_name=cprov.archive.distribution.name)
3078 sign_directory = os.path.join(
3079 self.parts_directory, cprov.archive.distribution.name, 'sign.d')
3080 with open(os.path.join(sign_directory, '10-sign'), 'w') as sign_script:
3081 sign_script.write(dedent("""\
3082 #! /bin/sh
3083 echo "$MODE signature of $INPUT_PATH ($DISTRIBUTION/$SUITE)" \\
3084 >"$OUTPUT_PATH"
3085 """))
3086 os.fchmod(sign_script.fileno(), 0o755)
3087
3088 self.setupPublisher(cprov.archive)
3089 self.archive_publisher._syncTimestamps = FakeMethod()
3090
3091 self._publishArchive(cprov.archive)
3092
3093 # Release exists.
3094 self.assertThat(self.release_file_path, PathExists())
3095
3096 # Release.gpg and InRelease exist with suitable fake signatures.
3097 # Note that the signatures are made before Release.new is renamed to
3098 # to Release.
3099 self.assertThat(
3100 self.release_file_signature_path,
3101 FileContains(
3102 "detached signature of %s.new (%s/breezy-autotest)\n" %
3103 (self.release_file_path, cprov.archive.distribution.name)))
3104 self.assertThat(
3105 self.inline_release_file_path,
3106 FileContains(
3107 "clear signature of %s.new (%s/breezy-autotest)\n" %
3108 (self.release_file_path, cprov.archive.distribution.name)))
3109
3110 # The publisher synchronises the various Release file timestamps.
3111 self.assertEqual(1, self.archive_publisher._syncTimestamps.call_count)
3112 sync_args = self.archive_publisher._syncTimestamps.extract_args()[0]
3113 self.assertEqual(self.distroseries.name, sync_args[0])
3114 self.assertThat(
3115 sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
3116
30683117
3069class TestPublisherLite(TestCaseWithFactory):3118class TestPublisherLite(TestCaseWithFactory):
3070 """Lightweight unit tests for the publisher."""3119 """Lightweight unit tests for the publisher."""
30713120
=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
--- lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:52 +0000
+++ lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:53 +0000
@@ -13,9 +13,11 @@
13from testtools.deferredruntest import AsynchronousDeferredRunTest13from testtools.deferredruntest import AsynchronousDeferredRunTest
14from testtools.matchers import (14from testtools.matchers import (
15 Contains,15 Contains,
16 Equals,
16 FileContains,17 FileContains,
17 Matcher,18 Matcher,
18 MatchesAll,19 MatchesAll,
20 MatchesDict,
19 Mismatch,21 Mismatch,
20 Not,22 Not,
21 StartsWith,23 StartsWith,
@@ -36,6 +38,7 @@
36 SigningUpload,38 SigningUpload,
37 UefiUpload,39 UefiUpload,
38 )40 )
41from lp.archivepublisher.tests.test_run_parts import RunPartsMixin
39from lp.services.osutils import write_file42from lp.services.osutils import write_file
40from lp.services.tarfile_helpers import LaunchpadWriteTarFile43from lp.services.tarfile_helpers import LaunchpadWriteTarFile
41from lp.soyuz.enums import ArchivePurpose44from lp.soyuz.enums import ArchivePurpose
@@ -215,7 +218,7 @@
215 return os.path.join(pubconf.archiveroot, "dists", self.suite, "main")218 return os.path.join(pubconf.archiveroot, "dists", self.suite, "main")
216219
217220
218class TestSigning(TestSigningHelpers):221class TestSigning(RunPartsMixin, TestSigningHelpers):
219222
220 def getSignedPath(self, loader_type, arch):223 def getSignedPath(self, loader_type, arch):
221 return os.path.join(self.getDistsPath(), "signed",224 return os.path.join(self.getDistsPath(), "signed",
@@ -934,6 +937,36 @@
934 "1.0/signed.tar.gz",937 "1.0/signed.tar.gz",
935 ]]))938 ]]))
936939
940 def test_checksumming_tree_signed_with_external_run_parts(self):
941 # Checksum files can be signed using an external run-parts helper.
942 # We disable subprocess.call because there's just too much going on,
943 # so we can't test this completely, but we can at least test that
944 # run_parts is called.
945 self.enableRunParts(distribution_name=self.distro.name)
946 run_parts_fixture = self.useFixture(MonkeyPatch(
947 "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod()))
948 self.setUpUefiKeys()
949 self.setUpKmodKeys()
950 self.setUpOpalKeys()
951 self.openArchive("test", "1.0", "amd64")
952 self.tarfile.add_file("1.0/empty.efi", "")
953 self.tarfile.add_file("1.0/empty.ko", "")
954 self.tarfile.add_file("1.0/empty.opal", "")
955 self.process_emulate()
956 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
957 "1.0", "SHA256SUMS")
958 self.assertTrue(os.path.exists(sha256file))
959 self.assertEqual(1, run_parts_fixture.new_value.call_count)
960 args, kwargs = run_parts_fixture.new_value.calls[-1]
961 self.assertEqual((self.distro.name, "sign.d"), args)
962 self.assertThat(kwargs["env"], MatchesDict({
963 "INPUT_PATH": Equals(sha256file),
964 "OUTPUT_PATH": Equals("%s.gpg" % sha256file),
965 "MODE": Equals("detached"),
966 "DISTRIBUTION": Equals(self.distro.name),
967 "SUITE": Equals(self.suite),
968 }))
969
937970
938class TestUefi(TestSigningHelpers):971class TestUefi(TestSigningHelpers):
939972