Merge lp:~cjwatson/launchpad/send-keys-to-builders into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18405
Proposed branch: lp:~cjwatson/launchpad/send-keys-to-builders
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/faster-archive-signing-key-tests
Diff against target: 1502 lines (+445/-123)
14 files modified
lib/lp/code/model/recipebuilder.py (+5/-4)
lib/lp/code/model/tests/test_recipebuilder.py (+51/-11)
lib/lp/services/tests/test_timeout.py (+10/-0)
lib/lp/services/timeout.py (+18/-0)
lib/lp/snappy/model/snapbuildbehaviour.py (+4/-3)
lib/lp/snappy/tests/test_snapbuildbehaviour.py (+59/-16)
lib/lp/soyuz/adapters/archivedependencies.py (+54/-13)
lib/lp/soyuz/adapters/tests/test_archivedependencies.py (+129/-61)
lib/lp/soyuz/model/binarypackagebuildbehaviour.py (+3/-2)
lib/lp/soyuz/model/livefsbuildbehaviour.py (+3/-2)
lib/lp/soyuz/tests/soyuz.py (+21/-1)
lib/lp/soyuz/tests/test_archive.py (+20/-3)
lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py (+30/-2)
lib/lp/soyuz/tests/test_livefsbuildbehaviour.py (+38/-5)
To merge this branch: bzr merge lp:~cjwatson/launchpad/send-keys-to-builders
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+323438@code.launchpad.net

Commit message

Send the necessary set of archive signing keys to builders.

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/code/model/recipebuilder.py'
2--- lib/lp/code/model/recipebuilder.py 2017-04-28 17:30:55 +0000
3+++ lib/lp/code/model/recipebuilder.py 2017-06-13 17:24:41 +0000
4@@ -67,10 +67,11 @@
5 args["ogrecomponent"] = get_primary_current_component(
6 self.build.archive, self.build.distroseries,
7 None).name
8- args['archives'] = yield get_sources_list_for_building(
9- self.build, distroarchseries, None,
10- tools_source=config.builddmaster.bzr_builder_sources_list,
11- logger=logger)
12+ args['archives'], args['trusted_keys'] = (
13+ yield get_sources_list_for_building(
14+ self.build, distroarchseries, None,
15+ tools_source=config.builddmaster.bzr_builder_sources_list,
16+ logger=logger))
17 args['archive_private'] = self.build.archive.private
18 args['distroseries_name'] = self.build.distroseries.name
19 if self.build.recipe.base_git_repository is not None:
20
21=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
22--- lib/lp/code/model/tests/test_recipebuilder.py 2017-04-28 17:30:55 +0000
23+++ lib/lp/code/model/tests/test_recipebuilder.py 2017-06-13 17:24:41 +0000
24@@ -5,16 +5,21 @@
25
26 __metaclass__ = type
27
28+import os.path
29 import shutil
30 import tempfile
31
32 from testtools.deferredruntest import AsynchronousDeferredRunTest
33+from testtools.matchers import MatchesListwise
34 import transaction
35 from twisted.internet import defer
36 from twisted.trial.unittest import TestCase as TrialTestCase
37 from zope.component import getUtility
38 from zope.security.proxy import removeSecurityProxy
39
40+from lp.archivepublisher.interfaces.archivesigningkey import (
41+ IArchiveSigningKey,
42+ )
43 from lp.buildmaster.enums import BuildStatus
44 from lp.buildmaster.interactor import BuilderInteractor
45 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
46@@ -40,13 +45,19 @@
47 from lp.soyuz.adapters.archivedependencies import (
48 get_sources_list_for_building,
49 )
50-from lp.soyuz.enums import ArchivePurpose
51+from lp.soyuz.enums import (
52+ ArchivePurpose,
53+ PackagePublishingStatus,
54+ )
55+from lp.soyuz.tests.soyuz import Base64KeyMatches
56 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
57 from lp.testing import (
58 person_logged_in,
59 TestCaseWithFactory,
60 )
61 from lp.testing.fakemethod import FakeMethod
62+from lp.testing.gpgkeys import gpgkeysdir
63+from lp.testing.keyserver import InProcessKeyServerFixture
64 from lp.testing.layers import LaunchpadZopelessLayer
65 from lp.testing.mail_helpers import pop_notifications
66
67@@ -59,7 +70,10 @@
68 archive=None, git=False):
69 """Create a sample `ISourcePackageRecipeBuild`."""
70 spn = self.factory.makeSourcePackageName("apackage")
71- distro = self.factory.makeDistribution(name="distro")
72+ if archive is None:
73+ distro = self.factory.makeDistribution(name="distro")
74+ else:
75+ distro = archive.distribution
76 distroseries = self.factory.makeDistroSeries(
77 name="mydistro", distribution=distro)
78 processor = getUtility(IProcessorSet).getByName('386')
79@@ -144,7 +158,7 @@
80
81 class TestAsyncRecipeBuilder(TestRecipeBuilderBase):
82
83- run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
84+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
85
86 def _setBuilderConfig(self):
87 """Setup a temporary builder config."""
88@@ -158,8 +172,9 @@
89 self._setBuilderConfig()
90 job = self.makeJob()
91 distroarchseries = job.build.distroseries.architectures[0]
92- expected_archives = yield get_sources_list_for_building(
93- job.build, distroarchseries, None)
94+ expected_archives, expected_trusted_keys = (
95+ yield get_sources_list_for_building(
96+ job.build, distroarchseries, None))
97 expected_archives.insert(
98 0, "deb http://foo %s main" % job.build.distroseries.name)
99 args = yield job._extraBuildArgs(distroarchseries)
100@@ -175,6 +190,7 @@
101 '# bzr-builder format 0.3 deb-version {debupstream}-0~{revno}\n'
102 'lp://dev/~joe/someapp/pkg\n',
103 'archives': expected_archives,
104+ 'trusted_keys': expected_trusted_keys,
105 'distroseries_name': job.build.distroseries.name,
106 }, args)
107
108@@ -249,8 +265,9 @@
109 # (note the missing 's' in %(series)
110 job = self.makeJob()
111 distroarchseries = job.build.distroseries.architectures[0]
112- expected_archives = yield get_sources_list_for_building(
113- job.build, distroarchseries, None)
114+ expected_archives, expected_trusted_keys = (
115+ yield get_sources_list_for_building(
116+ job.build, distroarchseries, None))
117 logger = BufferLogger()
118 extra_args = yield job._extraBuildArgs(distroarchseries, logger)
119 self.assertEqual({
120@@ -265,6 +282,7 @@
121 '# bzr-builder format 0.3 deb-version {debupstream}-0~{revno}\n'
122 'lp://dev/~joe/someapp/pkg\n',
123 'archives': expected_archives,
124+ 'trusted_keys': expected_trusted_keys,
125 'distroseries_name': job.build.distroseries.name,
126 }, extra_args)
127 self.assertIn(
128@@ -278,16 +296,19 @@
129 job = self.makeJob()
130 distroarchseries = job.build.distroseries.architectures[0]
131 args = yield job._extraBuildArgs(distroarchseries)
132- expected_archives = yield get_sources_list_for_building(
133- job.build, distroarchseries, None)
134+ expected_archives, expected_trusted_keys = (
135+ yield get_sources_list_for_building(
136+ job.build, distroarchseries, None))
137 self.assertEqual(args["archives"], expected_archives)
138+ self.assertEqual(args["trusted_keys"], expected_trusted_keys)
139
140 @defer.inlineCallbacks
141 def test_extraBuildArgs_git(self):
142 job = self.makeJob(git=True)
143 distroarchseries = job.build.distroseries.architectures[0]
144- expected_archives = yield get_sources_list_for_building(
145- job.build, distroarchseries, None)
146+ expected_archives, expected_trusted_keys = (
147+ yield get_sources_list_for_building(
148+ job.build, distroarchseries, None))
149 extra_args = yield job._extraBuildArgs(distroarchseries)
150 self.assertEqual({
151 'archive_private': False,
152@@ -302,11 +323,30 @@
153 '{debupstream}-0~{revtime}\n'
154 'lp:~joe/someapp/+git/pkg packaging\n',
155 'archives': expected_archives,
156+ 'trusted_keys': expected_trusted_keys,
157 'distroseries_name': job.build.distroseries.name,
158 'git': True,
159 }, extra_args)
160
161 @defer.inlineCallbacks
162+ def test_extraBuildArgs_archive_trusted_keys(self):
163+ # If the archive has a signing key, _extraBuildArgs sends it.
164+ yield self.useFixture(InProcessKeyServerFixture()).start()
165+ archive = self.factory.makeArchive()
166+ key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
167+ yield IArchiveSigningKey(archive).setSigningKey(
168+ key_path, async_keyserver=True)
169+ job = self.makeJob(archive=archive)
170+ distroarchseries = job.build.distroseries.architectures[0]
171+ self.factory.makeBinaryPackagePublishingHistory(
172+ distroarchseries=distroarchseries, pocket=job.build.pocket,
173+ archive=archive, status=PackagePublishingStatus.PUBLISHED)
174+ args = yield job._extraBuildArgs(distroarchseries)
175+ self.assertThat(args["trusted_keys"], MatchesListwise([
176+ Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
177+ ]))
178+
179+ @defer.inlineCallbacks
180 def test_composeBuildRequest(self):
181 job = self.makeJob()
182 test_publisher = SoyuzTestPublisher()
183
184=== modified file 'lib/lp/services/tests/test_timeout.py'
185--- lib/lp/services/tests/test_timeout.py 2016-07-01 20:07:09 +0000
186+++ lib/lp/services/tests/test_timeout.py 2017-06-13 17:24:41 +0000
187@@ -22,6 +22,7 @@
188 from testtools.matchers import MatchesStructure
189
190 from lp.services.timeout import (
191+ default_timeout,
192 get_default_timeout_function,
193 reduced_timeout,
194 set_default_timeout_function,
195@@ -207,6 +208,15 @@
196 no_default_timeout()
197 self.assertEqual([True], using_default)
198
199+ def test_default_timeout(self):
200+ """default_timeout sets the default timeout if none is set."""
201+ self.addCleanup(set_default_timeout_function, None)
202+ with default_timeout(1.0):
203+ self.assertEqual(1.0, get_default_timeout_function()())
204+ set_default_timeout_function(lambda: 5.0)
205+ with default_timeout(1.0):
206+ self.assertEqual(5.0, get_default_timeout_function()())
207+
208 def test_reduced_timeout(self):
209 """reduced_timeout caps the available timeout in various ways."""
210 self.addCleanup(set_default_timeout_function, None)
211
212=== modified file 'lib/lp/services/timeout.py'
213--- lib/lp/services/timeout.py 2017-05-05 11:48:31 +0000
214+++ lib/lp/services/timeout.py 2017-06-13 17:24:41 +0000
215@@ -5,6 +5,7 @@
216
217 __metaclass__ = type
218 __all__ = [
219+ "default_timeout",
220 "get_default_timeout_function",
221 "reduced_timeout",
222 "SafeTransportWithTimeout",
223@@ -57,6 +58,23 @@
224
225
226 @contextmanager
227+def default_timeout(default):
228+ """A context manager that sets the default timeout if none is set.
229+
230+ :param default: The default timeout to use if none is set.
231+ """
232+ original_timeout_function = get_default_timeout_function()
233+
234+ if original_timeout_function is None:
235+ set_default_timeout_function(lambda: default)
236+ try:
237+ yield
238+ finally:
239+ if original_timeout_function is None:
240+ set_default_timeout_function(None)
241+
242+
243+@contextmanager
244 def reduced_timeout(clearance, webapp_max=None, default=None):
245 """A context manager that reduces the default timeout.
246
247
248=== modified file 'lib/lp/snappy/model/snapbuildbehaviour.py'
249--- lib/lp/snappy/model/snapbuildbehaviour.py 2017-05-15 10:12:40 +0000
250+++ lib/lp/snappy/model/snapbuildbehaviour.py 2017-06-13 17:24:41 +0000
251@@ -99,9 +99,10 @@
252 args["arch_tag"] = build.distro_arch_series.architecturetag
253 # XXX cjwatson 2015-08-03: Allow tools_source to be overridden at
254 # some more fine-grained level.
255- args["archives"] = yield get_sources_list_for_building(
256- build, build.distro_arch_series, None,
257- tools_source=config.snappy.tools_source, logger=logger)
258+ args["archives"], args["trusted_keys"] = (
259+ yield get_sources_list_for_building(
260+ build, build.distro_arch_series, None,
261+ tools_source=config.snappy.tools_source, logger=logger))
262 args["archive_private"] = build.archive.private
263 if build.snap.branch is not None:
264 args["branch"] = build.snap.branch.bzr_identity
265
266=== modified file 'lib/lp/snappy/tests/test_snapbuildbehaviour.py'
267--- lib/lp/snappy/tests/test_snapbuildbehaviour.py 2017-05-15 10:12:40 +0000
268+++ lib/lp/snappy/tests/test_snapbuildbehaviour.py 2017-06-13 17:24:41 +0000
269@@ -6,6 +6,7 @@
270 __metaclass__ = type
271
272 from datetime import datetime
273+import os.path
274 from textwrap import dedent
275 import uuid
276
277@@ -17,13 +18,19 @@
278 from pymacaroons import Macaroon
279 from testtools import ExpectedException
280 from testtools.deferredruntest import AsynchronousDeferredRunTest
281-from testtools.matchers import IsInstance
282+from testtools.matchers import (
283+ IsInstance,
284+ MatchesListwise,
285+ )
286 import transaction
287 from twisted.internet import defer
288 from twisted.trial.unittest import TestCase as TrialTestCase
289 from zope.component import getUtility
290 from zope.security.proxy import removeSecurityProxy
291
292+from lp.archivepublisher.interfaces.archivesigningkey import (
293+ IArchiveSigningKey,
294+ )
295 from lp.buildmaster.enums import BuildStatus
296 from lp.buildmaster.interfaces.builder import CannotBuild
297 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
298@@ -48,8 +55,12 @@
299 from lp.soyuz.adapters.archivedependencies import (
300 get_sources_list_for_building,
301 )
302+from lp.soyuz.enums import PackagePublishingStatus
303 from lp.soyuz.interfaces.archive import ArchiveDisabled
304+from lp.soyuz.tests.soyuz import Base64KeyMatches
305 from lp.testing import TestCaseWithFactory
306+from lp.testing.gpgkeys import gpgkeysdir
307+from lp.testing.keyserver import InProcessKeyServerFixture
308 from lp.testing.layers import LaunchpadZopelessLayer
309
310
311@@ -60,9 +71,13 @@
312 super(TestSnapBuildBehaviourBase, self).setUp()
313 self.pushConfig("snappy", tools_source=None)
314
315- def makeJob(self, pocket=PackagePublishingPocket.UPDATES, **kwargs):
316+ def makeJob(self, archive=None, pocket=PackagePublishingPocket.UPDATES,
317+ **kwargs):
318 """Create a sample `ISnapBuildBehaviour`."""
319- distribution = self.factory.makeDistribution(name="distro")
320+ if archive is None:
321+ distribution = self.factory.makeDistribution(name="distro")
322+ else:
323+ distribution = archive.distribution
324 distroseries = self.factory.makeDistroSeries(
325 distribution=distribution, name="unstable")
326 processor = getUtility(IProcessorSet).getByName("386")
327@@ -70,7 +85,7 @@
328 distroseries=distroseries, architecturetag="i386",
329 processor=processor)
330 build = self.factory.makeSnapBuild(
331- distroarchseries=distroarchseries, pocket=pocket,
332+ archive=archive, distroarchseries=distroarchseries, pocket=pocket,
333 name=u"test-snap", **kwargs)
334 return IBuildFarmJobBehaviour(build)
335
336@@ -169,7 +184,7 @@
337
338
339 class TestAsyncSnapBuildBehaviour(TestSnapBuildBehaviourBase):
340- run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
341+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
342
343 def setUp(self):
344 super(TestAsyncSnapBuildBehaviour, self).setUp()
345@@ -212,8 +227,9 @@
346 # job for a Bazaar branch.
347 branch = self.factory.makeBranch()
348 job = self.makeJob(branch=branch)
349- expected_archives = yield get_sources_list_for_building(
350- job.build, job.build.distro_arch_series, None)
351+ expected_archives, expected_trusted_keys = (
352+ yield get_sources_list_for_building(
353+ job.build, job.build.distro_arch_series, None))
354 args = yield job._extraBuildArgs()
355 self.assertEqual({
356 "archive_private": False,
357@@ -223,6 +239,7 @@
358 "name": u"test-snap",
359 "proxy_url": self.proxy_url,
360 "revocation_endpoint": self.revocation_endpoint,
361+ "trusted_keys": expected_trusted_keys,
362 }, args)
363
364 @defer.inlineCallbacks
365@@ -231,8 +248,9 @@
366 # job for a Git branch.
367 [ref] = self.factory.makeGitRefs()
368 job = self.makeJob(git_ref=ref)
369- expected_archives = yield get_sources_list_for_building(
370- job.build, job.build.distro_arch_series, None)
371+ expected_archives, expected_trusted_keys = (
372+ yield get_sources_list_for_building(
373+ job.build, job.build.distro_arch_series, None))
374 args = yield job._extraBuildArgs()
375 self.assertEqual({
376 "archive_private": False,
377@@ -243,6 +261,7 @@
378 "name": u"test-snap",
379 "proxy_url": self.proxy_url,
380 "revocation_endpoint": self.revocation_endpoint,
381+ "trusted_keys": expected_trusted_keys,
382 }, args)
383
384 @defer.inlineCallbacks
385@@ -252,8 +271,9 @@
386 [ref] = self.factory.makeGitRefs()
387 removeSecurityProxy(ref.repository)._default_branch = ref.path
388 job = self.makeJob(git_ref=ref.repository.getRefByPath(u"HEAD"))
389- expected_archives = get_sources_list_for_building(
390- job.build, job.build.distro_arch_series, None)
391+ expected_archives, expected_trusted_keys = (
392+ yield get_sources_list_for_building(
393+ job.build, job.build.distro_arch_series, None))
394 args = yield job._extraBuildArgs()
395 self.assertEqual({
396 "archive_private": False,
397@@ -263,6 +283,7 @@
398 "name": u"test-snap",
399 "proxy_url": self.proxy_url,
400 "revocation_endpoint": self.revocation_endpoint,
401+ "trusted_keys": expected_trusted_keys,
402 }, args)
403
404 @defer.inlineCallbacks
405@@ -273,8 +294,9 @@
406 ref = self.factory.makeGitRefRemote(
407 repository_url=url, path=u"refs/heads/master")
408 job = self.makeJob(git_ref=ref)
409- expected_archives = yield get_sources_list_for_building(
410- job.build, job.build.distro_arch_series, None)
411+ expected_archives, expected_trusted_keys = (
412+ yield get_sources_list_for_building(
413+ job.build, job.build.distro_arch_series, None))
414 args = yield job._extraBuildArgs()
415 self.assertEqual({
416 "archive_private": False,
417@@ -285,6 +307,7 @@
418 "name": u"test-snap",
419 "proxy_url": self.proxy_url,
420 "revocation_endpoint": self.revocation_endpoint,
421+ "trusted_keys": expected_trusted_keys,
422 }, args)
423
424 @defer.inlineCallbacks
425@@ -294,8 +317,9 @@
426 url = u"https://git.example.org/foo"
427 ref = self.factory.makeGitRefRemote(repository_url=url, path=u"HEAD")
428 job = self.makeJob(git_ref=ref)
429- expected_archives = get_sources_list_for_building(
430- job.build, job.build.distro_arch_series, None)
431+ expected_archives, expected_trusted_keys = (
432+ yield get_sources_list_for_building(
433+ job.build, job.build.distro_arch_series, None))
434 args = yield job._extraBuildArgs()
435 self.assertEqual({
436 "archive_private": False,
437@@ -305,10 +329,29 @@
438 "name": u"test-snap",
439 "proxy_url": self.proxy_url,
440 "revocation_endpoint": self.revocation_endpoint,
441+ "trusted_keys": expected_trusted_keys,
442 }, args)
443
444 @defer.inlineCallbacks
445- def test_extraBuildArgs_proxy_url_set(self):
446+ def test_extraBuildArgs_archive_trusted_keys(self):
447+ # If the archive has a signing key, _extraBuildArgs sends it.
448+ yield self.useFixture(InProcessKeyServerFixture()).start()
449+ archive = self.factory.makeArchive()
450+ key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
451+ yield IArchiveSigningKey(archive).setSigningKey(
452+ key_path, async_keyserver=True)
453+ job = self.makeJob(archive=archive)
454+ self.factory.makeBinaryPackagePublishingHistory(
455+ distroarchseries=job.build.distro_arch_series,
456+ pocket=job.build.pocket, archive=archive,
457+ status=PackagePublishingStatus.PUBLISHED)
458+ args = yield job._extraBuildArgs()
459+ self.assertThat(args["trusted_keys"], MatchesListwise([
460+ Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
461+ ]))
462+
463+ @defer.inlineCallbacks
464+ def test_composeBuildRequest_proxy_url_set(self):
465 job = self.makeJob()
466 build_request = yield job.composeBuildRequest(None)
467 proxy_url = ("http://{username}:{password}"
468
469=== modified file 'lib/lp/soyuz/adapters/archivedependencies.py'
470--- lib/lp/soyuz/adapters/archivedependencies.py 2016-04-07 00:04:42 +0000
471+++ lib/lp/soyuz/adapters/archivedependencies.py 2017-06-13 17:24:41 +0000
472@@ -1,4 +1,4 @@
473-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
474+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
475 # GNU Affero General Public License version 3 (see the file LICENSE).
476
477 """Archive dependencies helper function.
478@@ -36,11 +36,15 @@
479 'pocket_dependencies',
480 ]
481
482+import base64
483 import logging
484 import traceback
485
486 from lazr.uri import URI
487+from twisted.internet import defer
488+from twisted.internet.threads import deferToThread
489 from zope.component import getUtility
490+from zope.security.proxy import removeSecurityProxy
491
492 from lp.app.errors import NotFoundError
493 from lp.registry.interfaces.distroseriesparent import IDistroSeriesParentSet
494@@ -48,6 +52,11 @@
495 PackagePublishingPocket,
496 pocketsuffix,
497 )
498+from lp.services.gpg.interfaces import (
499+ GPGKeyNotFoundError,
500+ IGPGHandler,
501+ )
502+from lp.services.timeout import default_timeout
503 from lp.soyuz.enums import (
504 ArchivePurpose,
505 PackagePublishingStatus,
506@@ -229,16 +238,19 @@
507 return deps
508
509
510+@defer.inlineCallbacks
511 def get_sources_list_for_building(build, distroarchseries, sourcepackagename,
512 tools_source=None, logger=None):
513- """Return the sources_list entries required to build the given item.
514+ """Return sources.list entries and keys required to build the given item.
515
516- The entries are returned in the order that is most useful;
517+ The sources.list entries are returned in the order that is most useful:
518 1. the context archive itself
519 2. external dependencies
520 3. user-selected archive dependencies
521 4. the default primary archive
522
523+ The keys are in an arbitrary order.
524+
525 :param build: a context `IBuild`.
526 :param distroarchseries: A `IDistroArchSeries`
527 :param sourcepackagename: A source package name (as text)
528@@ -246,14 +258,16 @@
529 additional dependency for build tools, just before the default
530 primary archive.
531 :param logger: an optional logger.
532- :return: a deb sources_list entries (lines).
533+ :return: a Deferred resolving to a tuple containing a list of deb
534+ sources.list entries (lines) and a list of base64-encoded public
535+ keys.
536 """
537 deps = expand_dependencies(
538 build.archive, distroarchseries, build.pocket,
539 build.current_component, sourcepackagename,
540 tools_source=tools_source, logger=logger)
541- sources_list_lines = \
542- _get_sources_list_for_dependencies(deps)
543+ sources_list_lines, trusted_keys = (
544+ yield _get_sources_list_for_dependencies(deps, logger=logger))
545
546 external_dep_lines = []
547 # Append external sources.list lines for this build if specified. No
548@@ -289,8 +303,9 @@
549 # binaries that need to override primary binaries of the same
550 # version), we want the external dependency lines to show up second:
551 # after the archive itself, but before any other dependencies.
552- return [sources_list_lines[0]] + external_dep_lines + \
553- sources_list_lines[1:]
554+ defer.returnValue(
555+ ([sources_list_lines[0]] + external_dep_lines + sources_list_lines[1:],
556+ trusted_keys))
557
558
559 def _has_published_binaries(archive, distroarchseries, pocket):
560@@ -323,8 +338,9 @@
561 return 'deb %s %s %s' % (url, suite, ' '.join(components))
562
563
564-def _get_sources_list_for_dependencies(dependencies):
565- """Return a list of sources_list lines.
566+@defer.inlineCallbacks
567+def _get_sources_list_for_dependencies(dependencies, logger=None):
568+ """Return sources.list entries and keys.
569
570 Process the given list of dependency tuples for the given
571 `DistroArchseries`.
572@@ -334,9 +350,15 @@
573 list of `IComponent` names)
574 :param distroarchseries: target `IDistroArchSeries`;
575
576- :return: a list of sources_list formatted lines.
577+ :return: a tuple containing a list of sources.list formatted lines and a
578+ list of base64-encoded public keys.
579 """
580 sources_list_lines = []
581+ trusted_keys = {}
582+ # The handler's security proxying doesn't protect anything useful here,
583+ # and the thread that we defer key retrieval to doesn't have an
584+ # interaction.
585+ gpghandler = removeSecurityProxy(getUtility(IGPGHandler))
586 for dep in dependencies:
587 if isinstance(dep, basestring):
588 sources_list_lines.append(dep)
589@@ -356,8 +378,27 @@
590 sources_list_line = _get_binary_sources_list_line(
591 archive, distro_arch_series, pocket, components)
592 sources_list_lines.append(sources_list_line)
593-
594- return sources_list_lines
595+ fingerprint = archive.signing_key_fingerprint
596+ if fingerprint is not None and fingerprint not in trusted_keys:
597+ def get_key():
598+ with default_timeout(15.0):
599+ try:
600+ return gpghandler.retrieveKey(fingerprint)
601+ except GPGKeyNotFoundError as e:
602+ # For now, just log this and proceed without the
603+ # key. We'll have to fix any outstanding cases
604+ # of this before we can switch to requiring
605+ # authentication across the board.
606+ if logger is not None:
607+ logger.warning(str(e))
608+ return None
609+
610+ key = yield deferToThread(get_key)
611+ if key is not None:
612+ trusted_keys[fingerprint] = base64.b64encode(key.export())
613+
614+ defer.returnValue(
615+ (sources_list_lines, [v for k, v in sorted(trusted_keys.items())]))
616
617
618 def _get_default_primary_dependencies(archive, distro_arch_series, component,
619
620=== modified file 'lib/lp/soyuz/adapters/tests/test_archivedependencies.py'
621--- lib/lp/soyuz/adapters/tests/test_archivedependencies.py 2017-04-25 11:36:10 +0000
622+++ lib/lp/soyuz/adapters/tests/test_archivedependencies.py 2017-06-13 17:24:41 +0000
623@@ -7,12 +7,23 @@
624
625 __metaclass__ = type
626
627-from testtools.matchers import StartsWith
628+import os.path
629+
630+from testtools.deferredruntest import AsynchronousDeferredRunTest
631+from testtools.matchers import (
632+ MatchesSetwise,
633+ StartsWith,
634+ )
635 import transaction
636+from twisted.internet import defer
637 from zope.component import getUtility
638
639+from lp.archivepublisher.interfaces.archivesigningkey import (
640+ IArchiveSigningKey,
641+ )
642 from lp.registry.interfaces.distribution import IDistributionSet
643 from lp.registry.interfaces.pocket import PackagePublishingPocket
644+from lp.services.config import config
645 from lp.services.log.logger import BufferLogger
646 from lp.soyuz.adapters.archivedependencies import (
647 default_component_dependency_name,
648@@ -25,8 +36,11 @@
649 from lp.soyuz.enums import PackagePublishingStatus
650 from lp.soyuz.interfaces.archive import IArchive
651 from lp.soyuz.interfaces.component import IComponentSet
652+from lp.soyuz.tests.soyuz import Base64KeyMatches
653 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
654 from lp.testing import TestCaseWithFactory
655+from lp.testing.gpgkeys import gpgkeysdir
656+from lp.testing.keyserver import InProcessKeyServerFixture
657 from lp.testing.layers import (
658 LaunchpadZopelessLayer,
659 ZopelessDatabaseLayer,
660@@ -111,10 +125,17 @@
661 """Test sources.list contents for building, and related mechanisms."""
662
663 layer = LaunchpadZopelessLayer
664+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
665
666 ubuntu_components = [
667 "main", "restricted", "universe", "multiverse", "partner"]
668
669+ fingerprints = {
670+ "ppa-sample@canonical.com": "0D57E99656BEFB0897606EE9A022DD1F5001B46D",
671+ "ppa-sample-4096@canonical.com": (
672+ "B7B1966662BA8D3F5A6ED89BD640F4A593B2CF67"),
673+ }
674+
675 def setUp(self):
676 super(TestSourcesList, self).setUp()
677 self.publisher = SoyuzTestPublisher()
678@@ -140,12 +161,22 @@
679 PackagePublishingPocket.UPDATES),
680 pocket_dependencies[default_pocket_dependency])
681
682- def makeArchive(self, publish_binary=False, **kwargs):
683+ @defer.inlineCallbacks
684+ def makeArchive(self, signing_key_name="ppa-sample@canonical.com",
685+ publish_binary=False, **kwargs):
686+ try:
687+ getattr(config, "in-process-key-server-fixture")
688+ except AttributeError:
689+ yield self.useFixture(InProcessKeyServerFixture()).start()
690 archive = self.factory.makeArchive(distribution=self.ubuntu, **kwargs)
691+ if signing_key_name is not None:
692+ key_path = os.path.join(gpgkeysdir, "%s.sec" % signing_key_name)
693+ yield IArchiveSigningKey(archive).setSigningKey(
694+ key_path, async_keyserver=True)
695 if publish_binary:
696 self.publisher.getPubBinaries(
697 archive=archive, status=PackagePublishingStatus.PUBLISHED)
698- return archive
699+ defer.returnValue(archive)
700
701 def makeBuild(self, **kwargs):
702 pub_source = self.publisher.getPubSource(**kwargs)
703@@ -159,77 +190,90 @@
704 build.archive, build.distro_series,
705 build.source_package_release.name).name)
706
707- def assertSourcesList(self, expected, build, **kwargs):
708+ @defer.inlineCallbacks
709+ def assertSourcesListAndKeys(self, expected_sources_list,
710+ expected_key_names, build, **kwargs):
711 expected_lines = []
712- for archive_or_prefix, suffixes in expected:
713+ for archive_or_prefix, suffixes in expected_sources_list:
714 if IArchive.providedBy(archive_or_prefix):
715 prefix = "deb %s " % archive_or_prefix.archive_url
716 else:
717 prefix = archive_or_prefix + " "
718 expected_lines.extend([prefix + suffix for suffix in suffixes])
719- sources_list = get_sources_list_for_building(
720+ sources_list, trusted_keys = yield get_sources_list_for_building(
721 build, build.distro_arch_series, build.source_package_release.name,
722 **kwargs)
723 self.assertEqual(expected_lines, sources_list)
724+ key_matchers = [
725+ Base64KeyMatches(self.fingerprints[key_name])
726+ for key_name in expected_key_names]
727+ self.assertThat(trusted_keys, MatchesSetwise(*key_matchers))
728
729+ @defer.inlineCallbacks
730 def test_ppa_with_no_binaries(self):
731 # If there are no published binaries in a PPA, only its primary
732 # archive dependencies need to be considered.
733- ppa = self.makeArchive()
734+ ppa = yield self.makeArchive()
735 build = self.makeBuild(archive=ppa)
736 self.assertEqual(
737 0, ppa.getAllPublishedBinaries(
738 distroarchseries=build.distro_arch_series,
739 status=PackagePublishingStatus.PUBLISHED).count())
740- self.assertSourcesList(
741+ yield self.assertSourcesListAndKeys(
742 [(self.ubuntu.main_archive, [
743 "hoary main restricted universe multiverse",
744 "hoary-security main restricted universe multiverse",
745 "hoary-updates main restricted universe multiverse",
746 ]),
747- ], build)
748+ ], [], build)
749
750+ @defer.inlineCallbacks
751 def test_ppa_with_binaries(self):
752 # If there are binaries published in a PPA, then the PPA is
753 # considered as well as its primary dependencies.
754- ppa = self.makeArchive(publish_binary=True)
755+ ppa = yield self.makeArchive(publish_binary=True)
756 build = self.makeBuild(archive=ppa)
757- self.assertSourcesList(
758+ yield self.assertSourcesListAndKeys(
759 [(ppa, ["hoary main"]),
760 (self.ubuntu.main_archive, [
761 "hoary main restricted universe multiverse",
762 "hoary-security main restricted universe multiverse",
763 "hoary-updates main restricted universe multiverse",
764 ]),
765- ], build)
766+ ], ["ppa-sample@canonical.com"], build)
767
768+ @defer.inlineCallbacks
769 def test_dependent_ppa_with_no_binaries(self):
770 # A depended-upon PPA is not considered if it has no published
771 # binaries.
772- lower_ppa = self.makeArchive()
773- upper_ppa = self.makeArchive(publish_binary=True)
774+ lower_ppa = yield self.makeArchive(
775+ signing_key_name="ppa-sample-4096@canonical.com")
776+ upper_ppa = yield self.makeArchive(publish_binary=True)
777 upper_ppa.addArchiveDependency(
778 lower_ppa, PackagePublishingPocket.RELEASE,
779 getUtility(IComponentSet)["main"])
780 build = self.makeBuild(archive=upper_ppa)
781- self.assertSourcesList(
782+ yield self.assertSourcesListAndKeys(
783 [(upper_ppa, ["hoary main"]),
784 (self.ubuntu.main_archive, [
785 "hoary main restricted universe multiverse",
786 "hoary-security main restricted universe multiverse",
787 "hoary-updates main restricted universe multiverse",
788 ]),
789- ], build)
790+ ], ["ppa-sample@canonical.com"], build)
791
792+ @defer.inlineCallbacks
793 def test_dependent_ppa_with_binaries(self):
794 # A depended-upon PPA is considered if it has published binaries.
795- lower_ppa = self.makeArchive(publish_binary=True)
796- upper_ppa = self.makeArchive(publish_binary=True)
797+ lower_ppa = yield self.makeArchive(
798+ signing_key_name="ppa-sample-4096@canonical.com",
799+ publish_binary=True)
800+ upper_ppa = yield self.makeArchive(publish_binary=True)
801 upper_ppa.addArchiveDependency(
802 lower_ppa, PackagePublishingPocket.RELEASE,
803 getUtility(IComponentSet)["main"])
804 build = self.makeBuild(archive=upper_ppa)
805- self.assertSourcesList(
806+ yield self.assertSourcesListAndKeys(
807 [(upper_ppa, ["hoary main"]),
808 (lower_ppa, ["hoary main"]),
809 (self.ubuntu.main_archive, [
810@@ -237,14 +281,19 @@
811 "hoary-security main restricted universe multiverse",
812 "hoary-updates main restricted universe multiverse",
813 ]),
814- ], build)
815+ ],
816+ ["ppa-sample@canonical.com", "ppa-sample-4096@canonical.com"],
817+ build)
818
819+ @defer.inlineCallbacks
820 def test_lax_supported_component_dependencies(self):
821 # Dependencies for series with
822 # strict_supported_component_dependencies=False are reasonable.
823 # PPAs only have the "main" component.
824- lower_ppa = self.makeArchive(publish_binary=True)
825- upper_ppa = self.makeArchive(publish_binary=True)
826+ lower_ppa = yield self.makeArchive(
827+ signing_key_name="ppa-sample-4096@canonical.com",
828+ publish_binary=True)
829+ upper_ppa = yield self.makeArchive(publish_binary=True)
830 upper_ppa.addArchiveDependency(
831 lower_ppa, PackagePublishingPocket.RELEASE,
832 getUtility(IComponentSet)["main"])
833@@ -252,7 +301,7 @@
834 self.ubuntu.main_archive, PackagePublishingPocket.UPDATES,
835 getUtility(IComponentSet)["restricted"])
836 build = self.makeBuild(archive=upper_ppa)
837- self.assertSourcesList(
838+ yield self.assertSourcesListAndKeys(
839 [(upper_ppa, ["hoary main"]),
840 (lower_ppa, ["hoary main"]),
841 (self.ubuntu.main_archive, [
842@@ -260,10 +309,12 @@
843 "hoary-security main restricted",
844 "hoary-updates main restricted",
845 ]),
846- ], build)
847+ ],
848+ ["ppa-sample@canonical.com", "ppa-sample-4096@canonical.com"],
849+ build)
850 self.hoary.strict_supported_component_dependencies = False
851 transaction.commit()
852- self.assertSourcesList(
853+ yield self.assertSourcesListAndKeys(
854 [(upper_ppa, ["hoary main"]),
855 (lower_ppa, ["hoary main"]),
856 (self.ubuntu.main_archive, [
857@@ -271,41 +322,45 @@
858 "hoary-security main restricted universe multiverse",
859 "hoary-updates main restricted universe multiverse",
860 ]),
861- ], build)
862+ ],
863+ ["ppa-sample@canonical.com", "ppa-sample-4096@canonical.com"],
864+ build)
865
866+ @defer.inlineCallbacks
867 def test_no_op_primary_archive_dependency(self):
868 # Overriding the default primary archive dependencies with exactly
869 # the same values has no effect.
870- ppa = self.makeArchive()
871+ ppa = yield self.makeArchive()
872 ppa.addArchiveDependency(
873 self.ubuntu.main_archive, PackagePublishingPocket.UPDATES,
874 getUtility(IComponentSet)["multiverse"])
875 build = self.makeBuild(archive=ppa)
876- self.assertSourcesList(
877+ yield self.assertSourcesListAndKeys(
878 [(self.ubuntu.main_archive, [
879 "hoary main restricted universe multiverse",
880 "hoary-security main restricted universe multiverse",
881 "hoary-updates main restricted universe multiverse",
882 ]),
883- ], build)
884+ ], [], build)
885
886+ @defer.inlineCallbacks
887 def test_primary_archive_dependency_security(self):
888 # The primary archive dependency can be modified to behave as an
889 # embargoed archive that builds security updates. This is done by
890 # setting the SECURITY pocket dependencies (RELEASE and SECURITY)
891 # and following the component dependencies of the component where
892 # the source was last published in the primary archive.
893- ppa = self.makeArchive()
894+ ppa = yield self.makeArchive()
895 ppa.addArchiveDependency(
896 self.ubuntu.main_archive, PackagePublishingPocket.SECURITY)
897 build = self.makeBuild(archive=ppa)
898 self.assertPrimaryCurrentComponent("universe", build)
899- self.assertSourcesList(
900+ yield self.assertSourcesListAndKeys(
901 [(self.ubuntu.main_archive, [
902 "hoary main universe",
903 "hoary-security main universe",
904 ]),
905- ], build)
906+ ], [], build)
907 self.publisher.getPubSource(
908 sourcename="with-ancestry", version="1.0",
909 archive=self.ubuntu.main_archive)
910@@ -313,59 +368,63 @@
911 sourcename="with-ancestry", version="1.1",
912 archive=ppa).createMissingBuilds()
913 self.assertPrimaryCurrentComponent("main", build_with_ancestry)
914- self.assertSourcesList(
915+ yield self.assertSourcesListAndKeys(
916 [(self.ubuntu.main_archive, [
917 "hoary main",
918 "hoary-security main",
919 ]),
920- ], build_with_ancestry)
921+ ], [], build_with_ancestry)
922
923+ @defer.inlineCallbacks
924 def test_primary_archive_dependency_release(self):
925 # The primary archive dependency can be modified to behave as a
926 # pristine build environment based only on what was included in the
927 # original release of the corresponding series.
928- ppa = self.makeArchive()
929+ ppa = yield self.makeArchive()
930 ppa.addArchiveDependency(
931 self.ubuntu.main_archive, PackagePublishingPocket.RELEASE,
932 getUtility(IComponentSet)["restricted"])
933 build = self.makeBuild(archive=ppa)
934- self.assertSourcesList(
935- [(self.ubuntu.main_archive, ["hoary main restricted"])], build)
936+ yield self.assertSourcesListAndKeys(
937+ [(self.ubuntu.main_archive, ["hoary main restricted"])], [], build)
938
939+ @defer.inlineCallbacks
940 def test_primary_archive_dependency_proposed(self):
941 # The primary archive dependency can be modified to extend the build
942 # environment for PROPOSED.
943- ppa = self.makeArchive()
944+ ppa = yield self.makeArchive()
945 ppa.addArchiveDependency(
946 self.ubuntu.main_archive, PackagePublishingPocket.PROPOSED,
947 getUtility(IComponentSet)["multiverse"])
948 build = self.makeBuild(archive=ppa)
949- self.assertSourcesList(
950+ yield self.assertSourcesListAndKeys(
951 [(self.ubuntu.main_archive, [
952 "hoary main restricted universe multiverse",
953 "hoary-security main restricted universe multiverse",
954 "hoary-updates main restricted universe multiverse",
955 "hoary-proposed main restricted universe multiverse",
956 ]),
957- ], build)
958+ ], [], build)
959
960+ @defer.inlineCallbacks
961 def test_primary_archive_dependency_backports(self):
962 # The primary archive dependency can be modified to extend the build
963 # environment for PROPOSED.
964- ppa = self.makeArchive()
965+ ppa = yield self.makeArchive()
966 ppa.addArchiveDependency(
967 self.ubuntu.main_archive, PackagePublishingPocket.BACKPORTS,
968 getUtility(IComponentSet)["multiverse"])
969 build = self.makeBuild(archive=ppa)
970- self.assertSourcesList(
971+ yield self.assertSourcesListAndKeys(
972 [(self.ubuntu.main_archive, [
973 "hoary main restricted universe multiverse",
974 "hoary-security main restricted universe multiverse",
975 "hoary-updates main restricted universe multiverse",
976 "hoary-backports main restricted universe multiverse",
977 ]),
978- ], build)
979+ ], [], build)
980
981+ @defer.inlineCallbacks
982 def test_partner(self):
983 # Similarly to what happens with PPA builds, partner builds may
984 # depend on any component in the primary archive. This behaviour
985@@ -377,15 +436,16 @@
986 archive=partner, component="partner",
987 status=PackagePublishingStatus.PUBLISHED)
988 build = self.makeBuild(archive=partner, component="partner")
989- self.assertSourcesList(
990+ yield self.assertSourcesListAndKeys(
991 [(partner, ["hoary partner"]),
992 (primary, [
993 "hoary main restricted universe multiverse",
994 "hoary-security main restricted universe multiverse",
995 "hoary-updates main restricted universe multiverse",
996 ]),
997- ], build)
998+ ], [], build)
999
1000+ @defer.inlineCallbacks
1001 def test_partner_proposed(self):
1002 # The partner archive's PROPOSED pocket builds against itself, but
1003 # still uses the default UPDATES dependency for the primary archive
1004@@ -401,7 +461,7 @@
1005 build = self.makeBuild(
1006 archive=partner, component="partner",
1007 pocket=PackagePublishingPocket.PROPOSED)
1008- self.assertSourcesList(
1009+ yield self.assertSourcesListAndKeys(
1010 [(partner, [
1011 "hoary partner",
1012 "hoary-proposed partner",
1013@@ -411,19 +471,20 @@
1014 "hoary-security main restricted universe multiverse",
1015 "hoary-updates main restricted universe multiverse",
1016 ]),
1017- ], build)
1018+ ], [], build)
1019
1020+ @defer.inlineCallbacks
1021 def test_archive_external_dependencies(self):
1022 # An archive can be manually given additional external dependencies.
1023 # If present, "%(series)s" is replaced with the series name for the
1024 # build being dispatched.
1025- ppa = self.makeArchive(publish_binary=True)
1026+ ppa = yield self.makeArchive(publish_binary=True)
1027 ppa.external_dependencies = (
1028 "deb http://user:pass@repository zoing everything\n"
1029 "deb http://user:pass@repository %(series)s public private\n"
1030 "deb http://user:pass@repository %(series)s-extra public")
1031 build = self.makeBuild(archive=ppa)
1032- self.assertSourcesList(
1033+ yield self.assertSourcesListAndKeys(
1034 [(ppa, ["hoary main"]),
1035 ("deb http://user:pass@repository", [
1036 "zoing everything",
1037@@ -435,16 +496,17 @@
1038 "hoary-security main restricted universe multiverse",
1039 "hoary-updates main restricted universe multiverse",
1040 ]),
1041- ], build)
1042+ ], ["ppa-sample@canonical.com"], build)
1043
1044+ @defer.inlineCallbacks
1045 def test_build_external_dependencies(self):
1046 # A single build can be manually given additional external
1047 # dependencies.
1048- ppa = self.makeArchive(publish_binary=True)
1049+ ppa = yield self.makeArchive(publish_binary=True)
1050 build = self.makeBuild(archive=ppa)
1051 build.api_external_dependencies = (
1052 "deb http://user:pass@repository foo bar")
1053- self.assertSourcesList(
1054+ yield self.assertSourcesListAndKeys(
1055 [(ppa, ["hoary main"]),
1056 ("deb http://user:pass@repository", ["foo bar"]),
1057 (self.ubuntu.main_archive, [
1058@@ -452,14 +514,15 @@
1059 "hoary-security main restricted universe multiverse",
1060 "hoary-updates main restricted universe multiverse",
1061 ]),
1062- ], build)
1063+ ], ["ppa-sample@canonical.com"], build)
1064
1065+ @defer.inlineCallbacks
1066 def test_build_tools(self):
1067 # We can force an extra build tools line to be added to
1068 # sources.list, which is useful for specialised build types.
1069- ppa = self.makeArchive(publish_binary=True)
1070+ ppa = yield self.makeArchive(publish_binary=True)
1071 build = self.makeBuild(archive=ppa)
1072- self.assertSourcesList(
1073+ yield self.assertSourcesListAndKeys(
1074 [(ppa, ["hoary main"]),
1075 ("deb http://example.org", ["hoary main"]),
1076 (self.ubuntu.main_archive, [
1077@@ -467,15 +530,18 @@
1078 "hoary-security main restricted universe multiverse",
1079 "hoary-updates main restricted universe multiverse",
1080 ]),
1081- ], build, tools_source="deb http://example.org %(series)s main")
1082+ ],
1083+ ["ppa-sample@canonical.com"], build,
1084+ tools_source="deb http://example.org %(series)s main")
1085
1086+ @defer.inlineCallbacks
1087 def test_build_tools_bad_formatting(self):
1088 # If tools_source is badly formatted, we log the error but don't
1089 # blow up. (Note the missing "s" at the end of "%(series)".)
1090- ppa = self.makeArchive(publish_binary=True)
1091+ ppa = yield self.makeArchive(publish_binary=True)
1092 build = self.makeBuild(archive=ppa)
1093 logger = BufferLogger()
1094- self.assertSourcesList(
1095+ yield self.assertSourcesListAndKeys(
1096 [(ppa, ["hoary main"]),
1097 (self.ubuntu.main_archive, [
1098 "hoary main restricted universe multiverse",
1099@@ -483,11 +549,13 @@
1100 "hoary-updates main restricted universe multiverse",
1101 ]),
1102 ],
1103- build, tools_source="deb http://example.org %(series) main",
1104+ ["ppa-sample@canonical.com"], build,
1105+ tools_source="deb http://example.org %(series) main",
1106 logger=logger)
1107 self.assertThat(logger.getLogBuffer(), StartsWith(
1108 "ERROR Exception processing build tools sources.list entry:\n"))
1109
1110+ @defer.inlineCallbacks
1111 def test_overlay(self):
1112 # An overlay distroseries is a derived distribution which works like
1113 # a PPA. This means that the parent's details gets added to the
1114@@ -508,10 +576,10 @@
1115 pocket=PackagePublishingPocket.SECURITY,
1116 component=getUtility(IComponentSet)["universe"])
1117 build = self.makeBuild()
1118- self.assertSourcesList(
1119+ yield self.assertSourcesListAndKeys(
1120 [(self.ubuntu.main_archive, ["hoary main"]),
1121 (depdistro.main_archive, [
1122 "depseries main universe",
1123 "depseries-security main universe",
1124 ]),
1125- ], build)
1126+ ], [], build)
1127
1128=== modified file 'lib/lp/soyuz/model/binarypackagebuildbehaviour.py'
1129--- lib/lp/soyuz/model/binarypackagebuildbehaviour.py 2017-04-28 17:30:55 +0000
1130+++ lib/lp/soyuz/model/binarypackagebuildbehaviour.py 2017-06-13 17:24:41 +0000
1131@@ -164,8 +164,9 @@
1132 args["ogrecomponent"] = (
1133 build.current_component.name)
1134
1135- args['archives'] = yield get_sources_list_for_building(
1136- build, das, build.source_package_release.name, logger=logger)
1137+ args['archives'], args['trusted_keys'] = (
1138+ yield get_sources_list_for_building(
1139+ build, das, build.source_package_release.name, logger=logger))
1140 args['archive_private'] = build.archive.private
1141 args['build_debug_symbols'] = build.archive.build_debug_symbols
1142
1143
1144=== modified file 'lib/lp/soyuz/model/livefsbuildbehaviour.py'
1145--- lib/lp/soyuz/model/livefsbuildbehaviour.py 2017-04-28 17:30:55 +0000
1146+++ lib/lp/soyuz/model/livefsbuildbehaviour.py 2017-06-13 17:24:41 +0000
1147@@ -90,8 +90,9 @@
1148 args["pocket"] = build.pocket.name.lower()
1149 args["arch_tag"] = build.distro_arch_series.architecturetag
1150 args["datestamp"] = build.version
1151- args["archives"] = yield get_sources_list_for_building(
1152- build, build.distro_arch_series, None, logger=logger)
1153+ args["archives"], args["trusted_keys"] = (
1154+ yield get_sources_list_for_building(
1155+ build, build.distro_arch_series, None, logger=logger))
1156 args["archive_private"] = build.archive.private
1157 defer.returnValue(args)
1158
1159
1160=== modified file 'lib/lp/soyuz/tests/soyuz.py'
1161--- lib/lp/soyuz/tests/soyuz.py 2015-09-28 17:38:45 +0000
1162+++ lib/lp/soyuz/tests/soyuz.py 2017-06-13 17:24:41 +0000
1163@@ -1,4 +1,4 @@
1164-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
1165+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
1166 # GNU Affero General Public License version 3 (see the file LICENSE).
1167
1168 """Helper functions/classes for Soyuz tests."""
1169@@ -6,14 +6,22 @@
1170 __metaclass__ = type
1171
1172 __all__ = [
1173+ 'Base64KeyMatches',
1174 'SoyuzTestHelper',
1175 ]
1176
1177+import base64
1178+
1179+from testtools.matchers import (
1180+ Equals,
1181+ Matcher,
1182+ )
1183 from zope.component import getUtility
1184
1185 from lp.registry.interfaces.distribution import IDistributionSet
1186 from lp.registry.interfaces.person import IPersonSet
1187 from lp.registry.interfaces.pocket import PackagePublishingPocket
1188+from lp.services.gpg.interfaces import IGPGHandler
1189 from lp.soyuz.enums import PackagePublishingStatus
1190 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
1191 from lp.testing.sampledata import (
1192@@ -94,3 +102,15 @@
1193 Return True if the lists matches, otherwise False.
1194 """
1195 return [p.id for p in expected] == [r.id for r in given]
1196+
1197+
1198+class Base64KeyMatches(Matcher):
1199+ """Matches if base64-encoded key material has a given fingerprint."""
1200+
1201+ def __init__(self, fingerprint):
1202+ self.fingerprint = fingerprint
1203+
1204+ def match(self, encoded_key):
1205+ key = base64.b64decode(encoded_key)
1206+ return Equals(self.fingerprint).match(
1207+ getUtility(IGPGHandler).importPublicKey(key).fingerprint)
1208
1209=== modified file 'lib/lp/soyuz/tests/test_archive.py'
1210--- lib/lp/soyuz/tests/test_archive.py 2017-04-28 17:30:55 +0000
1211+++ lib/lp/soyuz/tests/test_archive.py 2017-06-13 17:24:41 +0000
1212@@ -9,6 +9,7 @@
1213 timedelta,
1214 )
1215 import doctest
1216+import os.path
1217
1218 from pytz import UTC
1219 from testtools.deferredruntest import AsynchronousDeferredRunTest
1220@@ -16,6 +17,7 @@
1221 AllMatch,
1222 DocTestMatches,
1223 LessThan,
1224+ MatchesListwise,
1225 MatchesPredicate,
1226 MatchesRegex,
1227 MatchesStructure,
1228@@ -29,6 +31,9 @@
1229
1230 from lp.app.errors import NotFoundError
1231 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
1232+from lp.archivepublisher.interfaces.archivesigningkey import (
1233+ IArchiveSigningKey,
1234+ )
1235 from lp.buildmaster.enums import (
1236 BuildQueueStatus,
1237 BuildStatus,
1238@@ -108,6 +113,7 @@
1239 BinaryPackageReleaseDownloadCount,
1240 )
1241 from lp.soyuz.model.component import ComponentSelection
1242+from lp.soyuz.tests.soyuz import Base64KeyMatches
1243 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
1244 from lp.testing import (
1245 admin_logged_in,
1246@@ -120,6 +126,8 @@
1247 StormStatementRecorder,
1248 TestCaseWithFactory,
1249 )
1250+from lp.testing.gpgkeys import gpgkeysdir
1251+from lp.testing.keyserver import InProcessKeyServerFixture
1252 from lp.testing.layers import (
1253 DatabaseFunctionalLayer,
1254 LaunchpadFunctionalLayer,
1255@@ -1748,12 +1756,17 @@
1256 class TestArchiveDependencies(TestCaseWithFactory):
1257
1258 layer = LaunchpadZopelessLayer
1259- run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
1260+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
1261
1262 @defer.inlineCallbacks
1263 def test_private_sources_list(self):
1264 """Entries for private dependencies include credentials."""
1265 p3a = self.factory.makeArchive(name='p3a', private=True)
1266+ with InProcessKeyServerFixture() as keyserver:
1267+ yield keyserver.start()
1268+ key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
1269+ yield IArchiveSigningKey(p3a).setSigningKey(
1270+ key_path, async_keyserver=True)
1271 dependency = self.factory.makeArchive(
1272 name='dependency', private=True, owner=p3a.owner)
1273 with person_logged_in(p3a.owner):
1274@@ -1764,13 +1777,16 @@
1275 PackagePublishingPocket.RELEASE)
1276 build = self.factory.makeBinaryPackageBuild(archive=p3a,
1277 distroarchseries=bpph.distroarchseries)
1278- sources_list = yield get_sources_list_for_building(
1279+ sources_list, trusted_keys = yield get_sources_list_for_building(
1280 build, build.distro_arch_series,
1281 build.source_package_release.name)
1282 matches = MatchesRegex(
1283 "deb http://buildd:sekrit@private-ppa.launchpad.dev/"
1284 "person-name-.*/dependency/ubuntu distroseries-.* main")
1285 self.assertThat(sources_list[0], matches)
1286+ self.assertThat(trusted_keys, MatchesListwise([
1287+ Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
1288+ ]))
1289
1290 def test_invalid_external_dependencies(self):
1291 """Trying to set invalid external dependencies raises an exception."""
1292@@ -2018,7 +2034,7 @@
1293 self._createDep(
1294 test_publisher, series11, 'series12', 'depdistro4', 'multiverse',
1295 PackagePublishingPocket.UPDATES)
1296- sources_list = yield get_sources_list_for_building(
1297+ sources_list, trusted_keys = yield get_sources_list_for_building(
1298 build, build.distro_arch_series, build.source_package_release.name)
1299
1300 self.assertThat(
1301@@ -2040,6 +2056,7 @@
1302 ".../depdistro4 series12-updates "
1303 "main restricted universe multiverse\n",
1304 doctest.ELLIPSIS))
1305+ self.assertEqual([], trusted_keys)
1306
1307
1308 class TestComponents(TestCaseWithFactory):
1309
1310=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py'
1311--- lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py 2017-04-28 17:30:55 +0000
1312+++ lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py 2017-06-13 17:24:41 +0000
1313@@ -12,6 +12,7 @@
1314
1315 from storm.store import Store
1316 from testtools.deferredruntest import AsynchronousDeferredRunTest
1317+from testtools.matchers import MatchesListwise
1318 import transaction
1319 from twisted.internet import defer
1320 from twisted.trial.unittest import TestCase as TrialTestCase
1321@@ -19,6 +20,9 @@
1322 from zope.security.proxy import removeSecurityProxy
1323
1324 from lp.archivepublisher.diskpool import poolify
1325+from lp.archivepublisher.interfaces.archivesigningkey import (
1326+ IArchiveSigningKey,
1327+ )
1328 from lp.buildmaster.enums import (
1329 BuilderCleanStatus,
1330 BuildStatus,
1331@@ -55,9 +59,15 @@
1332 from lp.soyuz.adapters.archivedependencies import (
1333 get_sources_list_for_building,
1334 )
1335-from lp.soyuz.enums import ArchivePurpose
1336+from lp.soyuz.enums import (
1337+ ArchivePurpose,
1338+ PackagePublishingStatus,
1339+ )
1340+from lp.soyuz.tests.soyuz import Base64KeyMatches
1341 from lp.testing import TestCaseWithFactory
1342 from lp.testing.dbuser import switch_dbuser
1343+from lp.testing.gpgkeys import gpgkeysdir
1344+from lp.testing.keyserver import InProcessKeyServerFixture
1345 from lp.testing.layers import LaunchpadZopelessLayer
1346
1347
1348@@ -107,7 +117,7 @@
1349 das = build.distro_arch_series
1350 ds_name = das.distroseries.name
1351 suite = ds_name + pocketsuffix[build.pocket]
1352- archives = yield get_sources_list_for_building(
1353+ archives, trusted_keys = yield get_sources_list_for_building(
1354 build, das, build.source_package_release.name)
1355 arch_indep = das.isNominatedArchIndep
1356 if component is None:
1357@@ -131,6 +141,7 @@
1358 'ogrecomponent': component,
1359 'distribution': das.distroseries.distribution.name,
1360 'suite': suite,
1361+ 'trusted_keys': trusted_keys,
1362 }
1363 build_log = [
1364 ('build', build.build_cookie, 'binarypackage',
1365@@ -324,6 +335,23 @@
1366 extra_args = yield IBuildFarmJobBehaviour(build)._extraBuildArgs(build)
1367 self.assertTrue(extra_args['arch_indep'])
1368
1369+ @defer.inlineCallbacks
1370+ def test_extraBuildArgs_archive_trusted_keys(self):
1371+ # If the archive has a signing key, _extraBuildArgs sends it.
1372+ yield self.useFixture(InProcessKeyServerFixture()).start()
1373+ archive = self.factory.makeArchive()
1374+ key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
1375+ yield IArchiveSigningKey(archive).setSigningKey(
1376+ key_path, async_keyserver=True)
1377+ build = self.factory.makeBinaryPackageBuild(archive=archive)
1378+ self.factory.makeBinaryPackagePublishingHistory(
1379+ distroarchseries=build.distro_arch_series, pocket=build.pocket,
1380+ archive=archive, status=PackagePublishingStatus.PUBLISHED)
1381+ args = yield IBuildFarmJobBehaviour(build)._extraBuildArgs(build)
1382+ self.assertThat(args["trusted_keys"], MatchesListwise([
1383+ Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
1384+ ]))
1385+
1386 def test_verifyBuildRequest(self):
1387 # Don't allow a virtual build on a non-virtual builder.
1388 archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
1389
1390=== modified file 'lib/lp/soyuz/tests/test_livefsbuildbehaviour.py'
1391--- lib/lp/soyuz/tests/test_livefsbuildbehaviour.py 2017-04-28 17:30:55 +0000
1392+++ lib/lp/soyuz/tests/test_livefsbuildbehaviour.py 2017-06-13 17:24:41 +0000
1393@@ -6,16 +6,21 @@
1394 __metaclass__ = type
1395
1396 from datetime import datetime
1397+import os.path
1398
1399 import fixtures
1400 import pytz
1401 from testtools.deferredruntest import AsynchronousDeferredRunTest
1402+from testtools.matchers import MatchesListwise
1403 import transaction
1404 from twisted.internet import defer
1405 from twisted.trial.unittest import TestCase as TrialTestCase
1406 from zope.component import getUtility
1407 from zope.security.proxy import Proxy
1408
1409+from lp.archivepublisher.interfaces.archivesigningkey import (
1410+ IArchiveSigningKey,
1411+ )
1412 from lp.buildmaster.enums import BuildStatus
1413 from lp.buildmaster.interfaces.builder import CannotBuild
1414 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
1415@@ -38,13 +43,17 @@
1416 from lp.soyuz.adapters.archivedependencies import (
1417 get_sources_list_for_building,
1418 )
1419+from lp.soyuz.enums import PackagePublishingStatus
1420 from lp.soyuz.interfaces.archive import ArchiveDisabled
1421 from lp.soyuz.interfaces.livefs import (
1422 LIVEFS_FEATURE_FLAG,
1423 LiveFSBuildArchiveOwnerMismatch,
1424 )
1425 from lp.soyuz.model.livefsbuildbehaviour import LiveFSBuildBehaviour
1426+from lp.soyuz.tests.soyuz import Base64KeyMatches
1427 from lp.testing import TestCaseWithFactory
1428+from lp.testing.gpgkeys import gpgkeysdir
1429+from lp.testing.keyserver import InProcessKeyServerFixture
1430 from lp.testing.layers import LaunchpadZopelessLayer
1431
1432
1433@@ -56,9 +65,13 @@
1434 super(TestLiveFSBuildBehaviourBase, self).setUp()
1435 self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: u"on"}))
1436
1437- def makeJob(self, pocket=PackagePublishingPocket.RELEASE, **kwargs):
1438+ def makeJob(self, archive=None, pocket=PackagePublishingPocket.RELEASE,
1439+ **kwargs):
1440 """Create a sample `ILiveFSBuildBehaviour`."""
1441- distribution = self.factory.makeDistribution(name="distro")
1442+ if archive is None:
1443+ distribution = self.factory.makeDistribution(name="distro")
1444+ else:
1445+ distribution = archive.distribution
1446 distroseries = self.factory.makeDistroSeries(
1447 distribution=distribution, name="unstable")
1448 processor = getUtility(IProcessorSet).getByName("386")
1449@@ -66,7 +79,7 @@
1450 distroseries=distroseries, architecturetag="i386",
1451 processor=processor)
1452 build = self.factory.makeLiveFSBuild(
1453- distroarchseries=distroarchseries, pocket=pocket,
1454+ archive=archive, distroarchseries=distroarchseries, pocket=pocket,
1455 name=u"test-livefs", **kwargs)
1456 return IBuildFarmJobBehaviour(build)
1457
1458@@ -173,8 +186,9 @@
1459 job = self.makeJob(
1460 date_created=datetime(2014, 4, 25, 10, 38, 0, tzinfo=pytz.UTC),
1461 metadata={"project": "distro", "subproject": "special"})
1462- expected_archives = yield get_sources_list_for_building(
1463- job.build, job.build.distro_arch_series, None)
1464+ expected_archives, expected_trusted_keys = (
1465+ yield get_sources_list_for_building(
1466+ job.build, job.build.distro_arch_series, None))
1467 extra_args = yield job._extraBuildArgs()
1468 self.assertEqual({
1469 "archive_private": False,
1470@@ -185,6 +199,7 @@
1471 "project": "distro",
1472 "subproject": "special",
1473 "series": "unstable",
1474+ "trusted_keys": expected_trusted_keys,
1475 }, extra_args)
1476
1477 @defer.inlineCallbacks
1478@@ -209,6 +224,24 @@
1479 self.assertIsNot(Proxy, type(args["lb_args"]))
1480
1481 @defer.inlineCallbacks
1482+ def test_extraBuildArgs_archive_trusted_keys(self):
1483+ # If the archive has a signing key, _extraBuildArgs sends it.
1484+ yield self.useFixture(InProcessKeyServerFixture()).start()
1485+ archive = self.factory.makeArchive()
1486+ key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
1487+ yield IArchiveSigningKey(archive).setSigningKey(
1488+ key_path, async_keyserver=True)
1489+ job = self.makeJob(archive=archive)
1490+ self.factory.makeBinaryPackagePublishingHistory(
1491+ distroarchseries=job.build.distro_arch_series,
1492+ pocket=job.build.pocket, archive=archive,
1493+ status=PackagePublishingStatus.PUBLISHED)
1494+ args = yield job._extraBuildArgs()
1495+ self.assertThat(args["trusted_keys"], MatchesListwise([
1496+ Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
1497+ ]))
1498+
1499+ @defer.inlineCallbacks
1500 def test_composeBuildRequest(self):
1501 job = self.makeJob()
1502 lfa = self.factory.makeLibraryFileAlias(db_only=True)