Merge ~cjwatson/launchpad:generate-copy-archive-signing-keys into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: ddb45f5bb1c2197a2f05ced4652577bb1c893def
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:generate-copy-archive-signing-keys
Merge into: launchpad:master
Diff against target: 322 lines (+103/-24)
6 files modified
lib/lp/archivepublisher/archivegpgsigningkey.py (+12/-3)
lib/lp/archivepublisher/tests/archive-signing.txt (+35/-7)
lib/lp/soyuz/interfaces/archive.py (+4/-2)
lib/lp/soyuz/model/archive.py (+2/-2)
lib/lp/soyuz/scripts/ppakeygenerator.py (+11/-5)
lib/lp/soyuz/scripts/tests/test_ppakeygenerator.py (+39/-5)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+413406@code.launchpad.net

Commit message

Extend ppa-generate-keys to generate keys for copy archives too

Description of the change

It's currently awkward to run builds that use the output of test rebuilds, since they're only signed if we go through some manual steps to arrange that. Since the copy archive publisher has access to the signing service, having it generate signing keys is straightforward enough, so let's just do that.

This requires a separate invocation with the `--copy-archives` option (mainly since copy archives are published on a different machine so the signing keys need to be authorized for use by that machine).

Once we have this running automatically on production, we should change `Archive.can_be_published` to have a guard for copy archives similar to the one it has for PPAs: it should only publish production copy archives that have had a signing key generated for them.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/archivepublisher/archivegpgsigningkey.py b/lib/lp/archivepublisher/archivegpgsigningkey.py
2index c32bd9a..c1ada20 100644
3--- a/lib/lp/archivepublisher/archivegpgsigningkey.py
4+++ b/lib/lp/archivepublisher/archivegpgsigningkey.py
5@@ -251,7 +251,9 @@ class ArchiveGPGSigningKey(SignableArchive):
6 # Always generate signing keys for the default PPA, even if it
7 # was not specifically requested. The default PPA signing key
8 # is then propagated to the context named-ppa.
9- default_ppa = self.archive.owner.archive
10+ default_ppa = (
11+ self.archive.owner.archive if self.archive.is_ppa
12+ else self.archive)
13 if self.archive != default_ppa:
14 def propagate_key(_):
15 self.archive.signing_key_owner = default_ppa.signing_key_owner
16@@ -274,8 +276,15 @@ class ArchiveGPGSigningKey(SignableArchive):
17 propagate_key(None)
18 return
19
20- key_displayname = (
21- "Launchpad PPA for %s" % self.archive.owner.displayname)
22+ # XXX cjwatson 2021-12-17: If we need key generation for other
23+ # archive purposes (PRIMARY/PARTNER) then we should extend this, and
24+ # perhaps push it down to a property of the archive.
25+ if self.archive.is_copy:
26+ key_displayname = (
27+ "Launchpad copy archive %s" % self.archive.reference)
28+ else:
29+ key_displayname = (
30+ "Launchpad PPA for %s" % self.archive.owner.displayname)
31 if getFeatureFlag(PUBLISHER_GPG_USES_SIGNING_SERVICE):
32 try:
33 signing_key = getUtility(ISigningKeySet).generate(
34diff --git a/lib/lp/archivepublisher/tests/archive-signing.txt b/lib/lp/archivepublisher/tests/archive-signing.txt
35index f142a01..ced9aa5 100644
36--- a/lib/lp/archivepublisher/tests/archive-signing.txt
37+++ b/lib/lp/archivepublisher/tests/archive-signing.txt
38@@ -31,7 +31,7 @@ We will set up and use the test-keyserver.
39 Querying 'pending signing key' PPAs
40 -----------------------------------
41
42-`IArchiveSet.getPPAsPendingSigningKey` allows call-sites to query for
43+`IArchiveSet.getArchivesPendingSigningKey` allows call-sites to query for
44 PPA pending signing key generation.
45
46 >>> from lp.registry.interfaces.person import IPersonSet
47@@ -40,14 +40,14 @@ PPA pending signing key generation.
48 Only PPAs with at least one source publication are considered.
49
50 >>> archive_set = getUtility(IArchiveSet)
51- >>> for ppa in archive_set.getPPAsPendingSigningKey():
52+ >>> for ppa in archive_set.getArchivesPendingSigningKey():
53 ... print(ppa.displayname)
54 PPA for Celso Providelo
55 PPA for Mark Shuttleworth
56
57 The PPA for 'No Privileges' user exists, is enabled and has no
58 signing, but it also does not contain any source publications, that's
59-why it's skipped in the getPPAsPendingSigningKey() results.
60+why it's skipped in the getArchivesPendingSigningKey() results.
61
62 >>> cprov = getUtility(IPersonSet).getByName('cprov')
63 >>> no_priv = getUtility(IPersonSet).getByName('no-priv')
64@@ -71,7 +71,7 @@ we copy an arbitrary source into it.
65 >>> copied_sources = a_source.copyTo(
66 ... a_source.distroseries, a_source.pocket, no_priv.archive)
67
68- >>> for ppa in archive_set.getPPAsPendingSigningKey():
69+ >>> for ppa in archive_set.getArchivesPendingSigningKey():
70 ... print(ppa.displayname)
71 PPA for Celso Providelo
72 PPA for Mark Shuttleworth
73@@ -81,7 +81,7 @@ Disabled PPAs are excluded from the 'PendingSigningKey' pool:
74
75 >>> no_priv.archive.disable()
76
77- >>> for ppa in archive_set.getPPAsPendingSigningKey():
78+ >>> for ppa in archive_set.getArchivesPendingSigningKey():
79 ... print(ppa.displayname)
80 PPA for Celso Providelo
81 PPA for Mark Shuttleworth
82@@ -111,10 +111,34 @@ And use it as the Mark's PPA signing key.
83
84 It will exclude Mark's PPA from the 'PendingSigningKey' pool as well.
85
86- >>> for ppa in archive_set.getPPAsPendingSigningKey():
87+ >>> for ppa in archive_set.getArchivesPendingSigningKey():
88 ... print(ppa.displayname)
89 PPA for Celso Providelo
90
91+We can also query for copy archives.
92+
93+ >>> from lp.soyuz.enums import ArchivePurpose
94+ >>> rebuild_archive = factory.makeArchive(
95+ ... distribution=cprov.archive.distribution, name='test-rebuild',
96+ ... displayname='Test rebuild', purpose=ArchivePurpose.COPY)
97+ >>> _ = a_source.copyTo(
98+ ... a_source.distroseries, a_source.pocket, rebuild_archive)
99+ >>> for archive in archive_set.getArchivesPendingSigningKey(
100+ ... purpose=ArchivePurpose.COPY):
101+ ... print(archive.displayname)
102+ Test rebuild
103+
104+Set up a signing key for the new test rebuild archive, and after that it no
105+longer shows up as pending signing key generation.
106+
107+ >>> rebuild_archive.signing_key_owner = a_key.owner
108+ >>> rebuild_archive.signing_key_fingerprint = a_key.fingerprint
109+ >>> for archive in archive_set.getArchivesPendingSigningKey(
110+ ... purpose=ArchivePurpose.COPY):
111+ ... print(archive.displayname)
112+ >>> rebuild_archive.signing_key_owner = None
113+ >>> rebuild_archive.signing_key_fingerprint = None
114+
115
116 Generating a PPA signing key
117 ----------------------------
118@@ -270,7 +294,6 @@ As documented in archive.txt, when a named-ppa is created it is
119 already configured to used the same signing-key created for the
120 default PPA. We will create a named-ppa for Celso.
121
122- >>> from lp.soyuz.enums import ArchivePurpose
123 >>> named_ppa = getUtility(IArchiveSet).new(
124 ... owner=cprov, purpose=ArchivePurpose.PPA, name='boing')
125
126@@ -359,6 +382,11 @@ named after the user, even if the default PPA name is something different.
127 >>> print(named_ppa.signing_key.fingerprint)
128 447DBF38C4F9C4ED752246B77D88913717B05A8F
129
130+Keys generated for copy archives use a different naming scheme.
131+
132+ >>> IArchiveGPGSigningKey(rebuild_archive).generateSigningKey()
133+ Generating: Launchpad copy archive ubuntu/test-rebuild
134+
135 Restore the original functionality of GPGHandler.
136
137 >>> naked_gpghandler.generateKey = real_key_generator
138diff --git a/lib/lp/soyuz/interfaces/archive.py b/lib/lp/soyuz/interfaces/archive.py
139index d01ebdf..a3de895 100644
140--- a/lib/lp/soyuz/interfaces/archive.py
141+++ b/lib/lp/soyuz/interfaces/archive.py
142@@ -2416,10 +2416,12 @@ class IArchiveSet(Interface):
143 def getPPADistributionsForUser(user):
144 """Return the `Distribution`s of all PPAs for the given user."""
145
146- def getPPAsPendingSigningKey():
147- """Return all PPAs pending signing key generation.
148+ def getArchivesPendingSigningKey(purpose=ArchivePurpose.PPA):
149+ """Return all archives with `purpose` pending signing key generation.
150
151 The result is ordered by archive creation date.
152+
153+ :param purpose: Only return archives with this `ArchivePurpose`.
154 """
155
156 def getLatestPPASourcePublicationsForDistribution(distribution):
157diff --git a/lib/lp/soyuz/model/archive.py b/lib/lp/soyuz/model/archive.py
158index ae95cf9..50ed7aa 100644
159--- a/lib/lp/soyuz/model/archive.py
160+++ b/lib/lp/soyuz/model/archive.py
161@@ -2819,7 +2819,7 @@ class ArchiveSet:
162 self._getPPAsForUserClause(user))
163 return result.config(distinct=True)
164
165- def getPPAsPendingSigningKey(self):
166+ def getArchivesPendingSigningKey(self, purpose=ArchivePurpose.PPA):
167 """See `IArchiveSet`."""
168 origin = (
169 Archive,
170@@ -2828,7 +2828,7 @@ class ArchiveSet:
171 results = IStore(Archive).using(*origin).find(
172 Archive,
173 Archive.signing_key_fingerprint == None,
174- Archive.purpose == ArchivePurpose.PPA, Archive._enabled == True)
175+ Archive.purpose == purpose, Archive._enabled == True)
176 results.order_by(Archive.date_created)
177 return results.config(distinct=True)
178
179diff --git a/lib/lp/soyuz/scripts/ppakeygenerator.py b/lib/lp/soyuz/scripts/ppakeygenerator.py
180index 88e1d84..1b650f1 100644
181--- a/lib/lp/soyuz/scripts/ppakeygenerator.py
182+++ b/lib/lp/soyuz/scripts/ppakeygenerator.py
183@@ -1,4 +1,4 @@
184-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
185+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
186 # GNU Affero General Public License version 3 (see the file LICENSE).
187
188 __all__ = [
189@@ -14,6 +14,7 @@ from lp.services.scripts.base import (
190 LaunchpadCronScript,
191 LaunchpadScriptFailure,
192 )
193+from lp.soyuz.enums import ArchivePurpose
194 from lp.soyuz.interfaces.archive import IArchiveSet
195
196
197@@ -26,6 +27,9 @@ class PPAKeyGenerator(LaunchpadCronScript):
198 self.parser.add_option(
199 "-A", "--archive",
200 help="The reference of the archive whose key should be generated.")
201+ self.parser.add_option(
202+ "--copy-archives", action="store_true", default=False,
203+ help="Run only over COPY archives.")
204
205 def generateKey(self, archive):
206 """Generate a signing key for the given archive."""
207@@ -38,9 +42,9 @@ class PPAKeyGenerator(LaunchpadCronScript):
208
209 def main(self):
210 """Generate signing keys for the selected PPAs."""
211+ archive_set = getUtility(IArchiveSet)
212 if self.options.archive is not None:
213- archive = getUtility(IArchiveSet).getByReference(
214- self.options.archive)
215+ archive = archive_set.getByReference(self.options.archive)
216 if archive is None:
217 raise LaunchpadScriptFailure(
218 "No archive named '%s' could be found."
219@@ -51,9 +55,11 @@ class PPAKeyGenerator(LaunchpadCronScript):
220 % (archive.reference, archive.displayname,
221 archive.signing_key_fingerprint))
222 archives = [archive]
223+ elif self.options.copy_archives:
224+ archives = list(archive_set.getArchivesPendingSigningKey(
225+ purpose=ArchivePurpose.COPY))
226 else:
227- archive_set = getUtility(IArchiveSet)
228- archives = list(archive_set.getPPAsPendingSigningKey())
229+ archives = list(archive_set.getArchivesPendingSigningKey())
230
231 for archive in archives:
232 self.generateKey(archive)
233diff --git a/lib/lp/soyuz/scripts/tests/test_ppakeygenerator.py b/lib/lp/soyuz/scripts/tests/test_ppakeygenerator.py
234index 182f64a..0ad8a7d 100644
235--- a/lib/lp/soyuz/scripts/tests/test_ppakeygenerator.py
236+++ b/lib/lp/soyuz/scripts/tests/test_ppakeygenerator.py
237@@ -1,4 +1,4 @@
238-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
239+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
240 # GNU Affero General Public License version 3 (see the file LICENSE).
241
242 """`PPAKeyGenerator` script class tests."""
243@@ -10,14 +10,15 @@ from lp.registry.interfaces.gpg import IGPGKeySet
244 from lp.registry.interfaces.person import IPersonSet
245 from lp.services.propertycache import get_property_cache
246 from lp.services.scripts.base import LaunchpadScriptFailure
247+from lp.soyuz.enums import ArchivePurpose
248 from lp.soyuz.interfaces.archive import IArchiveSet
249 from lp.soyuz.scripts.ppakeygenerator import PPAKeyGenerator
250-from lp.testing import TestCase
251+from lp.testing import TestCaseWithFactory
252 from lp.testing.faketransaction import FakeTransaction
253 from lp.testing.layers import LaunchpadZopelessLayer
254
255
256-class TestPPAKeyGenerator(TestCase):
257+class TestPPAKeyGenerator(TestCaseWithFactory):
258 layer = LaunchpadZopelessLayer
259
260 def _fixArchiveForKeyGeneration(self, archive):
261@@ -29,7 +30,8 @@ class TestPPAKeyGenerator(TestCase):
262 ubuntutest = getUtility(IDistributionSet).getByName('ubuntutest')
263 archive.distribution = ubuntutest
264
265- def _getKeyGenerator(self, archive_reference=None, txn=None):
266+ def _getKeyGenerator(self, archive_reference=None, copy_archives=False,
267+ txn=None):
268 """Return a `PPAKeyGenerator` instance.
269
270 Monkey-patch the script object with a fake transaction manager
271@@ -40,6 +42,8 @@ class TestPPAKeyGenerator(TestCase):
272
273 if archive_reference is not None:
274 test_args.extend(['-A', archive_reference])
275+ if copy_archives:
276+ test_args.append('--copy-archives')
277
278 key_generator = PPAKeyGenerator(
279 name='ppa-generate-keys', test_args=test_args)
280@@ -109,7 +113,7 @@ class TestPPAKeyGenerator(TestCase):
281 The 'signing_key' for all 'pending-signing-key' PPAs are generated
282 and the transaction is committed once for each PPA.
283 """
284- archives = list(getUtility(IArchiveSet).getPPAsPendingSigningKey())
285+ archives = list(getUtility(IArchiveSet).getArchivesPendingSigningKey())
286
287 for archive in archives:
288 self._fixArchiveForKeyGeneration(archive)
289@@ -123,3 +127,33 @@ class TestPPAKeyGenerator(TestCase):
290 self.assertIsNotNone(archive.signing_key_fingerprint)
291
292 self.assertEqual(txn.commit_count, len(archives))
293+
294+ def testGenerateKeyForAllCopyArchives(self):
295+ """Signing key generation for all PPAs.
296+
297+ The 'signing_key' for all 'pending-signing-key' PPAs are generated
298+ and the transaction is committed once for each PPA.
299+ """
300+ for _ in range(3):
301+ rebuild = self.factory.makeArchive(
302+ distribution=getUtility(IDistributionSet).getByName(
303+ 'ubuntutest'),
304+ purpose=ArchivePurpose.COPY)
305+ self.factory.makeSourcePackagePublishingHistory(archive=rebuild)
306+
307+ archives = list(getUtility(IArchiveSet).getArchivesPendingSigningKey(
308+ purpose=ArchivePurpose.COPY))
309+ self.assertNotEqual([], archives)
310+
311+ for archive in archives:
312+ self._fixArchiveForKeyGeneration(archive)
313+ self.assertIsNone(archive.signing_key_fingerprint)
314+
315+ txn = FakeTransaction()
316+ key_generator = self._getKeyGenerator(copy_archives=True, txn=txn)
317+ key_generator.main()
318+
319+ for archive in archives:
320+ self.assertIsNotNone(archive.signing_key_fingerprint)
321+
322+ self.assertEqual(txn.commit_count, len(archives))

Subscribers

People subscribed via source and target branches

to status/vote changes: