Merge ~cjwatson/launchpad:issue-private-archive-macaroons into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: c7054367d530f8d2188f5330a9ebbf0163ce88fe
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:issue-private-archive-macaroons
Merge into: launchpad:master
Prerequisite: ~cjwatson/launchpad:get-sources-list-accept-behaviour
Diff against target: 402 lines (+147/-24)
10 files modified
lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py (+9/-0)
lib/lp/buildmaster/model/buildfarmjobbehaviour.py (+4/-0)
lib/lp/oci/model/ocirecipebuildbehaviour.py (+9/-5)
lib/lp/snappy/model/snapbuildbehaviour.py (+8/-4)
lib/lp/snappy/tests/test_snapbuildbehaviour.py (+55/-0)
lib/lp/soyuz/adapters/archivedependencies.py (+11/-8)
lib/lp/soyuz/model/binarypackagebuildbehaviour.py (+10/-0)
lib/lp/soyuz/model/livefsbuildbehaviour.py (+9/-0)
lib/lp/soyuz/tests/test_archive.py (+29/-7)
system-packages.txt (+3/-0)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+401323@code.launchpad.net

Commit message

Dispatch sources.list entries for private archives using macaroons

Description of the change

This allows snap base archive dependencies on private archives to work (for 16.04 ESM), and also takes a significant step towards abolishing `Archive.buildd_secret`. These tokens are only valid while the build is running, and so are much less vulnerable to accidental leakage.

The old buildd secret is still used in the file map passed to builders to fetch the source package for binary package builds in private archives, so it can't yet be removed entirely.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py b/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
index 4d768d8..d210d64 100644
--- a/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
+++ b/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
@@ -46,6 +46,15 @@ class IBuildFarmJobBehaviour(Interface):
46 'password' (optional): password to authenticate with46 'password' (optional): password to authenticate with
47 """47 """
4848
49 def issueMacaroon():
50 """Issue a macaroon to access private resources for this build.
51
52 :raises NotImplementedError: if the build type does not support
53 accessing private resources.
54 :return: A Deferred that calls back with a serialized macaroon or a
55 fault.
56 """
57
49 def extraBuildArgs(logger=None):58 def extraBuildArgs(logger=None):
50 """Return extra arguments required by the builder for this build.59 """Return extra arguments required by the builder for this build.
5160
diff --git a/lib/lp/buildmaster/model/buildfarmjobbehaviour.py b/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
index f8b5034..e0d5380 100644
--- a/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
+++ b/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
@@ -89,6 +89,10 @@ class BuildFarmJobBehaviourBase:
89 """The default behaviour is to send no files."""89 """The default behaviour is to send no files."""
90 return {}90 return {}
9191
92 def issueMacaroon(self):
93 raise NotImplementedError(
94 "This build type does not support accessing private resources.")
95
92 def extraBuildArgs(self, logger=None):96 def extraBuildArgs(self, logger=None):
93 """The default behaviour is to send only common extra arguments."""97 """The default behaviour is to send only common extra arguments."""
94 args = {}98 args = {}
diff --git a/lib/lp/oci/model/ocirecipebuildbehaviour.py b/lib/lp/oci/model/ocirecipebuildbehaviour.py
index e8a60f5..0ca766d 100644
--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
@@ -80,6 +80,14 @@ class OCIRecipeBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
80 raise CannotBuild(80 raise CannotBuild(
81 "Missing chroot for %s" % build.distro_arch_series.displayname)81 "Missing chroot for %s" % build.distro_arch_series.displayname)
8282
83 def issueMacaroon(self):
84 """See `IBuildFarmJobBehaviour`."""
85 return cancel_on_timeout(
86 self._authserver.callRemote(
87 "issueMacaroon",
88 "oci-recipe-build", "OCIRecipeBuild", self.build.id),
89 config.builddmaster.authentication_timeout)
90
83 def _getBuildInfoArgs(self):91 def _getBuildInfoArgs(self):
84 def format_user(user):92 def format_user(user):
85 if user is None:93 if user is None:
@@ -142,11 +150,7 @@ class OCIRecipeBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
142150
143 if build.recipe.git_ref is not None:151 if build.recipe.git_ref is not None:
144 if build.recipe.git_repository.private:152 if build.recipe.git_repository.private:
145 macaroon_raw = yield cancel_on_timeout(153 macaroon_raw = yield self.issueMacaroon()
146 self._authserver.callRemote(
147 "issueMacaroon",
148 "oci-recipe-build", "OCIRecipeBuild", build.id),
149 config.builddmaster.authentication_timeout)
150 url = build.recipe.git_repository.getCodebrowseUrl(154 url = build.recipe.git_repository.getCodebrowseUrl(
151 username=None, password=macaroon_raw)155 username=None, password=macaroon_raw)
152 args["git_repository"] = url156 args["git_repository"] = url
diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
index dda949b..0057670 100644
--- a/lib/lp/snappy/model/snapbuildbehaviour.py
+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
@@ -145,6 +145,13 @@ class SnapBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
145 raise CannotBuild(145 raise CannotBuild(
146 "Missing chroot for %s" % build.distro_arch_series.displayname)146 "Missing chroot for %s" % build.distro_arch_series.displayname)
147147
148 def issueMacaroon(self):
149 """See `IBuildFarmJobBehaviour`."""
150 return cancel_on_timeout(
151 self._authserver.callRemote(
152 "issueMacaroon", "snap-build", "SnapBuild", self.build.id),
153 config.builddmaster.authentication_timeout)
154
148 @defer.inlineCallbacks155 @defer.inlineCallbacks
149 def extraBuildArgs(self, logger=None):156 def extraBuildArgs(self, logger=None):
150 """157 """
@@ -186,10 +193,7 @@ class SnapBuildBehaviour(SnapProxyMixin, BuildFarmJobBehaviourBase):
186 if build.snap.git_ref.repository_url is not None:193 if build.snap.git_ref.repository_url is not None:
187 args["git_repository"] = build.snap.git_ref.repository_url194 args["git_repository"] = build.snap.git_ref.repository_url
188 elif build.snap.git_repository.private:195 elif build.snap.git_repository.private:
189 macaroon_raw = yield cancel_on_timeout(196 macaroon_raw = yield self.issueMacaroon()
190 self._authserver.callRemote(
191 "issueMacaroon", "snap-build", "SnapBuild", build.id),
192 config.builddmaster.authentication_timeout)
193 url = build.snap.git_repository.getCodebrowseUrl(197 url = build.snap.git_repository.getCodebrowseUrl(
194 username=None, password=macaroon_raw)198 username=None, password=macaroon_raw)
195 args["git_repository"] = url199 args["git_repository"] = url
diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
index 1d80326..0deebed 100644
--- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py
+++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
@@ -14,6 +14,7 @@ from textwrap import dedent
14import time14import time
15import uuid15import uuid
1616
17from aptsources.sourceslist import SourceEntry
17import fixtures18import fixtures
18from pymacaroons import Macaroon19from pymacaroons import Macaroon
19import pytz20import pytz
@@ -77,6 +78,7 @@ from lp.services.log.logger import (
77 BufferLogger,78 BufferLogger,
78 DevNullLogger,79 DevNullLogger,
79 )80 )
81from lp.services.macaroons.testing import MacaroonVerifies
80from lp.services.statsd.tests import StatsMixin82from lp.services.statsd.tests import StatsMixin
81from lp.services.webapp import canonical_url83from lp.services.webapp import canonical_url
82from lp.snappy.interfaces.snap import (84from lp.snappy.interfaces.snap import (
@@ -790,6 +792,59 @@ class TestAsyncSnapBuildBehaviour(StatsMixin, TestSnapBuildBehaviourBase):
790 self.assertEqual(expected_archives, args["archives"])792 self.assertEqual(expected_archives, args["archives"])
791793
792 @defer.inlineCallbacks794 @defer.inlineCallbacks
795 def test_extraBuildArgs_snap_base_with_private_archive_dependencies(self):
796 # If the build is using a snap base that has archive dependencies on
797 # private PPAs, extraBuildArgs sends them.
798 self.useFixture(InProcessAuthServerFixture())
799 self.pushConfig(
800 "launchpad", internal_macaroon_secret_key="some-secret")
801 snap_base = self.factory.makeSnapBase()
802 job = self.makeJob(snap_base=snap_base)
803 dependency = self.factory.makeArchive(
804 distribution=job.archive.distribution, private=True)
805 snap_base.addArchiveDependency(
806 dependency, PackagePublishingPocket.RELEASE,
807 getUtility(IComponentSet)["main"])
808 self.factory.makeBinaryPackagePublishingHistory(
809 archive=dependency, distroarchseries=job.build.distro_arch_series,
810 pocket=PackagePublishingPocket.RELEASE,
811 status=PackagePublishingStatus.PUBLISHED)
812 with dbuser(config.builddmaster.dbuser):
813 args = yield job.extraBuildArgs()
814 job.build.updateStatus(BuildStatus.BUILDING)
815 self.assertThat(
816 [SourceEntry(item) for item in args["archives"]],
817 MatchesListwise([
818 MatchesStructure(
819 type=Equals("deb"),
820 uri=AfterPreprocessing(urlsplit, MatchesStructure(
821 scheme=Equals("http"),
822 username=Equals("buildd"),
823 password=MacaroonVerifies("snap-build", dependency),
824 hostname=Equals("private-ppa.launchpad.test"),
825 path=Equals("/%s/%s/%s" % (
826 dependency.owner.name, dependency.name,
827 dependency.distribution.name)))),
828 dist=Equals(job.build.distro_series.name),
829 comps=Equals(["main"])),
830 MatchesStructure.byEquality(
831 type="deb",
832 uri=job.archive.archive_url,
833 dist=job.build.distro_series.name,
834 comps=["main", "universe"]),
835 MatchesStructure.byEquality(
836 type="deb",
837 uri=job.archive.archive_url,
838 dist="%s-security" % job.build.distro_series.name,
839 comps=["main", "universe"]),
840 MatchesStructure.byEquality(
841 type="deb",
842 uri=job.archive.archive_url,
843 dist="%s-updates" % job.build.distro_series.name,
844 comps=["main", "universe"]),
845 ]))
846
847 @defer.inlineCallbacks
793 def test_extraBuildArgs_ppa_and_snap_base_with_archive_dependencies(self):848 def test_extraBuildArgs_ppa_and_snap_base_with_archive_dependencies(self):
794 # If the build is using a PPA and a snap base that both have archive849 # If the build is using a PPA and a snap base that both have archive
795 # dependencies, extraBuildArgs sends them all.850 # dependencies, extraBuildArgs sends them all.
diff --git a/lib/lp/soyuz/adapters/archivedependencies.py b/lib/lp/soyuz/adapters/archivedependencies.py
index ff1d117..685a82e 100644
--- a/lib/lp/soyuz/adapters/archivedependencies.py
+++ b/lib/lp/soyuz/adapters/archivedependencies.py
@@ -290,7 +290,8 @@ def get_sources_list_for_building(behaviour, distroarchseries,
290 tools_source=tools_source, tools_fingerprint=tools_fingerprint,290 tools_source=tools_source, tools_fingerprint=tools_fingerprint,
291 logger=logger)291 logger=logger)
292 sources_list_lines, trusted_keys = (292 sources_list_lines, trusted_keys = (
293 yield _get_sources_list_for_dependencies(deps, logger=logger))293 yield _get_sources_list_for_dependencies(
294 behaviour, deps, logger=logger))
294295
295 external_dep_lines = []296 external_dep_lines = []
296 # Append external sources.list lines for this build if specified. No297 # Append external sources.list lines for this build if specified. No
@@ -343,7 +344,8 @@ def _has_published_binaries(archive, distroarchseries, pocket):
343 return not published_binaries.is_empty()344 return not published_binaries.is_empty()
344345
345346
346def _get_binary_sources_list_line(archive, distroarchseries, pocket,347@defer.inlineCallbacks
348def _get_binary_sources_list_line(behaviour, archive, distroarchseries, pocket,
347 components):349 components):
348 """Return the correponding binary sources_list line."""350 """Return the correponding binary sources_list line."""
349 # Encode the private PPA repository password in the351 # Encode the private PPA repository password in the
@@ -351,23 +353,24 @@ def _get_binary_sources_list_line(archive, distroarchseries, pocket,
351 # sanitized to not expose it.353 # sanitized to not expose it.
352 if archive.private:354 if archive.private:
353 uri = URI(archive.archive_url)355 uri = URI(archive.archive_url)
354 uri = uri.replace(356 macaroon_raw = yield behaviour.issueMacaroon()
355 userinfo="buildd:%s" % archive.buildd_secret)357 uri = uri.replace(userinfo="buildd:%s" % macaroon_raw)
356 url = str(uri)358 url = str(uri)
357 else:359 else:
358 url = archive.archive_url360 url = archive.archive_url
359361
360 suite = distroarchseries.distroseries.name + pocketsuffix[pocket]362 suite = distroarchseries.distroseries.name + pocketsuffix[pocket]
361 return 'deb %s %s %s' % (url, suite, ' '.join(components))363 defer.returnValue('deb %s %s %s' % (url, suite, ' '.join(components)))
362364
363365
364@defer.inlineCallbacks366@defer.inlineCallbacks
365def _get_sources_list_for_dependencies(dependencies, logger=None):367def _get_sources_list_for_dependencies(behaviour, dependencies, logger=None):
366 """Return sources.list entries and keys.368 """Return sources.list entries and keys.
367369
368 Process the given list of dependency tuples for the given370 Process the given list of dependency tuples for the given
369 `DistroArchseries`.371 `DistroArchseries`.
370372
373 :param behaviour: the build's `IBuildFarmJobBehaviour`.
371 :param dependencies: list of 3 elements tuples as:374 :param dependencies: list of 3 elements tuples as:
372 (`IArchive`, `IDistroArchSeries`, `PackagePublishingPocket`,375 (`IArchive`, `IDistroArchSeries`, `PackagePublishingPocket`,
373 list of `IComponent` names)376 list of `IComponent` names)
@@ -399,8 +402,8 @@ def _get_sources_list_for_dependencies(dependencies, logger=None):
399 components = [402 components = [
400 component for component in components403 component for component in components
401 if component in archive_components]404 if component in archive_components]
402 sources_list_line = _get_binary_sources_list_line(405 sources_list_line = yield _get_binary_sources_list_line(
403 archive, distro_arch_series, pocket, components)406 behaviour, archive, distro_arch_series, pocket, components)
404 sources_list_lines.append(sources_list_line)407 sources_list_lines.append(sources_list_line)
405 fingerprint = archive.signing_key_fingerprint408 fingerprint = archive.signing_key_fingerprint
406 if fingerprint is not None and fingerprint not in trusted_keys:409 if fingerprint is not None and fingerprint not in trusted_keys:
diff --git a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
index 6b74f46..2876ac0 100644
--- a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
+++ b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
@@ -22,6 +22,8 @@ from lp.buildmaster.model.buildfarmjobbehaviour import (
22 BuildFarmJobBehaviourBase,22 BuildFarmJobBehaviourBase,
23 )23 )
24from lp.registry.interfaces.pocket import PackagePublishingPocket24from lp.registry.interfaces.pocket import PackagePublishingPocket
25from lp.services.config import config
26from lp.services.twistedsupport import cancel_on_timeout
25from lp.services.webapp import urlappend27from lp.services.webapp import urlappend
26from lp.soyuz.adapters.archivedependencies import (28from lp.soyuz.adapters.archivedependencies import (
27 get_primary_current_component,29 get_primary_current_component,
@@ -131,6 +133,14 @@ class BinaryPackageBuildBehaviour(BuildFarmJobBehaviourBase):
131 (build.title, build.id, build.pocket.name,133 (build.title, build.id, build.pocket.name,
132 build.distro_series.name))134 build.distro_series.name))
133135
136 def issueMacaroon(self):
137 """See `IBuildFarmJobBehaviour`."""
138 return cancel_on_timeout(
139 self._authserver.callRemote(
140 "issueMacaroon", "binary-package-build", "BinaryPackageBuild",
141 self.build.id),
142 config.builddmaster.authentication_timeout)
143
134 @defer.inlineCallbacks144 @defer.inlineCallbacks
135 def extraBuildArgs(self, logger=None):145 def extraBuildArgs(self, logger=None):
136 """146 """
diff --git a/lib/lp/soyuz/model/livefsbuildbehaviour.py b/lib/lp/soyuz/model/livefsbuildbehaviour.py
index c439559..0f93d39 100644
--- a/lib/lp/soyuz/model/livefsbuildbehaviour.py
+++ b/lib/lp/soyuz/model/livefsbuildbehaviour.py
@@ -25,6 +25,8 @@ from lp.buildmaster.model.buildfarmjobbehaviour import (
25 BuildFarmJobBehaviourBase,25 BuildFarmJobBehaviourBase,
26 )26 )
27from lp.registry.interfaces.series import SeriesStatus27from lp.registry.interfaces.series import SeriesStatus
28from lp.services.config import config
29from lp.services.twistedsupport import cancel_on_timeout
28from lp.soyuz.adapters.archivedependencies import (30from lp.soyuz.adapters.archivedependencies import (
29 get_sources_list_for_building,31 get_sources_list_for_building,
30 )32 )
@@ -78,6 +80,13 @@ class LiveFSBuildBehaviour(BuildFarmJobBehaviourBase):
78 raise CannotBuild(80 raise CannotBuild(
79 "Missing chroot for %s" % build.distro_arch_series.displayname)81 "Missing chroot for %s" % build.distro_arch_series.displayname)
8082
83 def issueMacaroon(self):
84 """See `IBuildFarmJobBehaviour`."""
85 return cancel_on_timeout(
86 self._authserver.callRemote(
87 "issueMacaroon", "livefs-build", "LiveFSBuild", self.build.id),
88 config.builddmaster.authentication_timeout)
89
81 @defer.inlineCallbacks90 @defer.inlineCallbacks
82 def extraBuildArgs(self, logger=None):91 def extraBuildArgs(self, logger=None):
83 """92 """
diff --git a/lib/lp/soyuz/tests/test_archive.py b/lib/lp/soyuz/tests/test_archive.py
index a49ccd3..2d53207 100644
--- a/lib/lp/soyuz/tests/test_archive.py
+++ b/lib/lp/soyuz/tests/test_archive.py
@@ -13,22 +13,28 @@ from datetime import (
13import doctest13import doctest
14import os.path14import os.path
1515
16from aptsources.sourceslist import SourceEntry
16from pytz import UTC17from pytz import UTC
17import responses18import responses
18import six19import six
19from six.moves import http_client20from six.moves import http_client
21from six.moves.urllib.parse import urlsplit
20from storm.store import Store22from storm.store import Store
21from testtools.matchers import (23from testtools.matchers import (
24 AfterPreprocessing,
22 AllMatch,25 AllMatch,
23 DocTestMatches,26 DocTestMatches,
27 Equals,
24 LessThan,28 LessThan,
25 MatchesListwise,29 MatchesListwise,
26 MatchesPredicate,30 MatchesPredicate,
27 MatchesRegex,
28 MatchesStructure,31 MatchesStructure,
29 )32 )
30from testtools.testcase import ExpectedException33from testtools.testcase import ExpectedException
31from testtools.twistedsupport import AsynchronousDeferredRunTest34from testtools.twistedsupport import (
35 AsynchronousDeferredRunTest,
36 AsynchronousDeferredRunTestForBrokenTwisted,
37 )
32import transaction38import transaction
33from twisted.internet import defer39from twisted.internet import defer
34from zope.component import getUtility40from zope.component import getUtility
@@ -57,6 +63,7 @@ from lp.registry.interfaces.person import IPersonSet
57from lp.registry.interfaces.pocket import PackagePublishingPocket63from lp.registry.interfaces.pocket import PackagePublishingPocket
58from lp.registry.interfaces.series import SeriesStatus64from lp.registry.interfaces.series import SeriesStatus
59from lp.registry.interfaces.teammembership import TeamMembershipStatus65from lp.registry.interfaces.teammembership import TeamMembershipStatus
66from lp.services.authserver.testing import InProcessAuthServerFixture
60from lp.services.database.interfaces import IStore67from lp.services.database.interfaces import IStore
61from lp.services.database.sqlbase import sqlvalues68from lp.services.database.sqlbase import sqlvalues
62from lp.services.features import getFeatureFlag69from lp.services.features import getFeatureFlag
@@ -67,6 +74,7 @@ from lp.services.gpg.interfaces import (
67 IGPGHandler,74 IGPGHandler,
68 )75 )
69from lp.services.job.interfaces.job import JobStatus76from lp.services.job.interfaces.job import JobStatus
77from lp.services.macaroons.testing import MacaroonVerifies
70from lp.services.propertycache import (78from lp.services.propertycache import (
71 clear_property_cache,79 clear_property_cache,
72 get_property_cache,80 get_property_cache,
@@ -1866,11 +1874,15 @@ class TestAddArchiveDependencies(TestCaseWithFactory):
1866class TestArchiveDependencies(TestCaseWithFactory):1874class TestArchiveDependencies(TestCaseWithFactory):
18671875
1868 layer = LaunchpadZopelessLayer1876 layer = LaunchpadZopelessLayer
1869 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)1877 run_tests_with = AsynchronousDeferredRunTestForBrokenTwisted.make_factory(
1878 timeout=30)
18701879
1871 @defer.inlineCallbacks1880 @defer.inlineCallbacks
1872 def test_private_sources_list(self):1881 def test_private_sources_list(self):
1873 """Entries for private dependencies include credentials."""1882 """Entries for private dependencies include credentials."""
1883 self.useFixture(InProcessAuthServerFixture())
1884 self.pushConfig(
1885 "launchpad", internal_macaroon_secret_key="some-secret")
1874 p3a = self.factory.makeArchive(name='p3a', private=True)1886 p3a = self.factory.makeArchive(name='p3a', private=True)
1875 yield self.useFixture(InProcessKeyServerFixture()).start()1887 yield self.useFixture(InProcessKeyServerFixture()).start()
1876 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')1888 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
@@ -1890,10 +1902,20 @@ class TestArchiveDependencies(TestCaseWithFactory):
1890 sources_list, trusted_keys = yield get_sources_list_for_building(1902 sources_list, trusted_keys = yield get_sources_list_for_building(
1891 behaviour, build.distro_arch_series,1903 behaviour, build.distro_arch_series,
1892 build.source_package_release.name)1904 build.source_package_release.name)
1893 matches = MatchesRegex(1905 # Mark the build as building so that we can verify its macaroons.
1894 "deb http://buildd:sekrit@private-ppa.launchpad.test/"1906 build.updateStatus(BuildStatus.BUILDING)
1895 "person-name-.*/dependency/ubuntu distroseries-.* main")1907 self.assertThat(SourceEntry(sources_list[0]), MatchesStructure(
1896 self.assertThat(sources_list[0], matches)1908 type=Equals("deb"),
1909 uri=AfterPreprocessing(urlsplit, MatchesStructure(
1910 scheme=Equals("http"),
1911 username=Equals("buildd"),
1912 password=MacaroonVerifies("binary-package-build", p3a),
1913 hostname=Equals("private-ppa.launchpad.test"),
1914 path=Equals("/%s/dependency/ubuntu" % p3a.owner.name),
1915 )),
1916 dist=Equals(build.distro_series.getSuite(build.pocket)),
1917 comps=Equals(["main"]),
1918 ))
1897 self.assertThat(trusted_keys, MatchesListwise([1919 self.assertThat(trusted_keys, MatchesListwise([
1898 Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),1920 Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
1899 ]))1921 ]))
diff --git a/system-packages.txt b/system-packages.txt
index 84a8548..56a1291 100644
--- a/system-packages.txt
+++ b/system-packages.txt
@@ -19,6 +19,9 @@ apt
19apt_inst19apt_inst
20apt_pkg20apt_pkg
2121
22# Used by tests to parse sources.list entries.
23aptsources
24
22# utilities/js-deps25# utilities/js-deps
23convoy26convoy
2427

Subscribers

People subscribed via source and target branches

to status/vote changes: