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