Merge lp:~apw/launchpad/generify-uefi-signing into lp:launchpad
- generify-uefi-signing
- Merge into devel
Proposed by
Andy Whitcroft
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 18029 | ||||
Proposed branch: | lp:~apw/launchpad/generify-uefi-signing | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
853 lines (+244/-105) 15 files modified
lib/lp/archivepublisher/config.py (+12/-7) lib/lp/archivepublisher/signing.py (+44/-25) lib/lp/archivepublisher/tests/test_config.py (+42/-12) lib/lp/archivepublisher/tests/test_ftparchive.py (+6/-2) lib/lp/archivepublisher/tests/test_signing.py (+71/-26) lib/lp/archiveuploader/nascentuploadfile.py (+8/-6) lib/lp/archiveuploader/tests/test_nascentuploadfile.py (+7/-1) lib/lp/archiveuploader/tests/test_uploadpolicy.py (+22/-1) lib/lp/soyuz/browser/queue.py (+2/-2) lib/lp/soyuz/configure.zcml (+1/-0) lib/lp/soyuz/enums.py (+3/-3) lib/lp/soyuz/interfaces/queue.py (+5/-2) lib/lp/soyuz/model/queue.py (+8/-6) lib/lp/soyuz/scripts/custom_uploads_copier.py (+6/-5) lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py (+7/-7) |
||||
To merge this branch: | bzr merge lp:~apw/launchpad/generify-uefi-signing | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+293802@code.launchpad.net |
Commit message
Generify the UEFI signing custom upload. Switch it to be a signing upload initially only supporting UEFI signing as before. Include backwards compatibility such that raw-uefi and raw-signing are both supported.
Description of the change
Generify the UEFI signing custom upload. Switch it to be a signing upload initially only supporting UEFI signing as before. Include backwards compatibility such that raw-uefi and raw-signing are both supported. Publication is into dists in the "signed" directory. Where existing configuration exists under "uefi" we will continue to use that. We also expose dists "signed" as dists "uefi" for compatibility with existing *-signed packages.
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Fixing
Revision history for this message
Andy Whitcroft (apw) wrote : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/archivepublisher/config.py' |
2 | --- lib/lp/archivepublisher/config.py 2016-04-27 10:39:03 +0000 |
3 | +++ lib/lp/archivepublisher/config.py 2016-05-11 15:32:04 +0000 |
4 | @@ -78,16 +78,21 @@ |
5 | pubconf.miscroot = None |
6 | |
7 | if archive.is_main: |
8 | - pubconf.uefiroot = pubconf.archiveroot + '-uefi' |
9 | - pubconf.uefiautokey = False |
10 | + pubconf.signingroot = pubconf.archiveroot + '-uefi' |
11 | + if not os.path.exists(pubconf.signingroot): |
12 | + pubconf.signingroot = pubconf.archiveroot + '-signing' |
13 | + pubconf.signingautokey = False |
14 | elif archive.is_ppa: |
15 | - pubconf.uefiroot = os.path.join( |
16 | - ppa_config.signing_keys_root, "uefi", |
17 | + signing_keys_root = os.path.join(ppa_config.signing_keys_root, "uefi") |
18 | + if not os.path.exists(signing_keys_root): |
19 | + signing_keys_root = os.path.join( |
20 | + ppa_config.signing_keys_root, "signing") |
21 | + pubconf.signingroot = os.path.join(signing_keys_root, |
22 | archive.owner.name, archive.name) |
23 | - pubconf.uefiautokey = True |
24 | + pubconf.signingautokey = True |
25 | else: |
26 | - pubconf.uefiroot = None |
27 | - pubconf.uefiautokey = False |
28 | + pubconf.signingroot = None |
29 | + pubconf.signingautokey = False |
30 | |
31 | pubconf.poolroot = os.path.join(pubconf.archiveroot, 'pool') |
32 | pubconf.distsroot = os.path.join(pubconf.archiveroot, 'dists') |
33 | |
34 | === renamed file 'lib/lp/archivepublisher/uefi.py' => 'lib/lp/archivepublisher/signing.py' |
35 | --- lib/lp/archivepublisher/uefi.py 2016-05-03 09:30:51 +0000 |
36 | +++ lib/lp/archivepublisher/signing.py 2016-05-11 15:32:04 +0000 |
37 | @@ -1,7 +1,7 @@ |
38 | -# Copyright 2012-2013 Canonical Ltd. This software is licensed under the |
39 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
40 | # GNU Affero General Public License version 3 (see the file LICENSE). |
41 | |
42 | -"""The processing of UEFI boot loader images. |
43 | +"""The processing of Signing tarballs. |
44 | |
45 | UEFI Secure Boot requires boot loader images to be signed, and we want to |
46 | have signed images in the archive so that they can be used for upgrades. |
47 | @@ -12,8 +12,8 @@ |
48 | __metaclass__ = type |
49 | |
50 | __all__ = [ |
51 | - "process_uefi", |
52 | - "UefiUpload", |
53 | + "process_signing", |
54 | + "SigningUpload", |
55 | ] |
56 | |
57 | import os |
58 | @@ -23,32 +23,32 @@ |
59 | from lp.services.osutils import remove_if_exists |
60 | |
61 | |
62 | -class UefiUpload(CustomUpload): |
63 | - """UEFI boot loader custom upload. |
64 | +class SigningUpload(CustomUpload): |
65 | + """Signing custom upload. |
66 | |
67 | The filename must be of the form: |
68 | |
69 | - <TYPE>_<VERSION>_<ARCH>.tar.gz |
70 | + <PACKAGE>_<VERSION>_<ARCH>.tar.gz |
71 | |
72 | where: |
73 | |
74 | - * TYPE: loader type (e.g. 'efilinux'); |
75 | + * PACKAGE: source package of the contents; |
76 | * VERSION: encoded version; |
77 | * ARCH: targeted architecture tag (e.g. 'amd64'). |
78 | |
79 | The contents are extracted in the archive in the following path: |
80 | |
81 | - <ARCHIVE>/dists/<SUITE>/main/uefi/<TYPE>-<ARCH>/<VERSION> |
82 | + <ARCHIVE>/dists/<SUITE>/main/signed/<PACKAGE>-<ARCH>/<VERSION> |
83 | |
84 | A 'current' symbolic link points to the most recent version. The |
85 | tarfile must contain at least one file matching the wildcard *.efi, and |
86 | any such files are signed using the archive's UEFI signing key. |
87 | |
88 | - Signing keys may be installed in the "uefiroot" directory specified in |
89 | + Signing keys may be installed in the "signingroot" directory specified in |
90 | publisher configuration. In this directory, the private key is |
91 | "uefi.key" and the certificate is "uefi.crt". |
92 | """ |
93 | - custom_type = "UEFI" |
94 | + custom_type = "signing" |
95 | |
96 | @staticmethod |
97 | def parsePath(tarfile_path): |
98 | @@ -59,32 +59,51 @@ |
99 | return bits[0], bits[1], bits[2].split(".")[0] |
100 | |
101 | def setComponents(self, tarfile_path): |
102 | - self.loader_type, self.version, self.arch = self.parsePath( |
103 | + self.package, self.version, self.arch = self.parsePath( |
104 | tarfile_path) |
105 | |
106 | def setTargetDirectory(self, pubconf, tarfile_path, distroseries): |
107 | - if pubconf.uefiroot is None: |
108 | + if pubconf.signingroot is None: |
109 | if self.logger is not None: |
110 | - self.logger.warning("No UEFI root configured for this archive") |
111 | + self.logger.warning( |
112 | + "No signing root configured for this archive") |
113 | self.key = None |
114 | self.cert = None |
115 | self.autokey = False |
116 | else: |
117 | - self.key = os.path.join(pubconf.uefiroot, "uefi.key") |
118 | - self.cert = os.path.join(pubconf.uefiroot, "uefi.crt") |
119 | - self.autokey = pubconf.uefiautokey |
120 | + self.key = os.path.join(pubconf.signingroot, "uefi.key") |
121 | + self.cert = os.path.join(pubconf.signingroot, "uefi.crt") |
122 | + self.autokey = pubconf.signingautokey |
123 | |
124 | self.setComponents(tarfile_path) |
125 | + |
126 | + # Ensure we expose the results via uefi and signed in dists. |
127 | + # If we already have a uefi directory move it to signed else |
128 | + # make a new signed. For compatibility ensure we have uefi |
129 | + # symlink to signed. |
130 | + # NOTE: we rely on "signed" and "uefi" being in the same directory. |
131 | + dists_signed = os.path.join( |
132 | + pubconf.archiveroot, "dists", distroseries, "main", "signed") |
133 | + dists_uefi = os.path.join( |
134 | + pubconf.archiveroot, "dists", distroseries, "main", "uefi") |
135 | + if not os.path.exists(dists_signed): |
136 | + if os.path.isdir(dists_uefi): |
137 | + os.rename(dists_uefi, dists_signed) |
138 | + else: |
139 | + os.makedirs(dists_signed, 0o755) |
140 | + if not os.path.exists(dists_uefi): |
141 | + os.symlink("signed", dists_uefi) |
142 | + |
143 | + # Extract into the "signed" path regardless of linking. |
144 | self.targetdir = os.path.join( |
145 | - pubconf.archiveroot, "dists", distroseries, "main", "uefi", |
146 | - "%s-%s" % (self.loader_type, self.arch)) |
147 | + dists_signed, "%s-%s" % (self.package, self.arch)) |
148 | self.archiveroot = pubconf.archiveroot |
149 | |
150 | @classmethod |
151 | def getSeriesKey(cls, tarfile_path): |
152 | try: |
153 | - loader_type, _, arch = cls.parsePath(tarfile_path) |
154 | - return loader_type, arch |
155 | + package, _, arch = cls.parsePath(tarfile_path) |
156 | + return package, arch |
157 | except ValueError: |
158 | return None |
159 | |
160 | @@ -169,7 +188,7 @@ |
161 | |
162 | No actual extraction is required. |
163 | """ |
164 | - super(UefiUpload, self).extract() |
165 | + super(SigningUpload, self).extract() |
166 | efi_filenames = list(self.findEfiFilenames()) |
167 | for efi_filename in efi_filenames: |
168 | remove_if_exists("%s.signed" % efi_filename) |
169 | @@ -179,12 +198,12 @@ |
170 | return filename.startswith("%s/" % self.version) |
171 | |
172 | |
173 | -def process_uefi(pubconf, tarfile_path, distroseries, logger=None): |
174 | - """Process a raw-uefi tarfile. |
175 | +def process_signing(pubconf, tarfile_path, distroseries, logger=None): |
176 | + """Process a raw-uefi/raw-signing tarfile. |
177 | |
178 | Unpacking it into the given archive for the given distroseries. |
179 | Raises CustomUploadError (or some subclass thereof) if anything goes |
180 | wrong. |
181 | """ |
182 | - upload = UefiUpload(logger=logger) |
183 | + upload = SigningUpload(logger=logger) |
184 | upload.process(pubconf, tarfile_path, distroseries) |
185 | |
186 | === modified file 'lib/lp/archivepublisher/tests/test_config.py' |
187 | --- lib/lp/archivepublisher/tests/test_config.py 2016-05-03 11:11:39 +0000 |
188 | +++ lib/lp/archivepublisher/tests/test_config.py 2016-05-11 15:32:04 +0000 |
189 | @@ -8,6 +8,8 @@ |
190 | |
191 | __metaclass__ = type |
192 | |
193 | +import os |
194 | + |
195 | from zope.component import getUtility |
196 | |
197 | from lp.archivepublisher.config import getPubConfig |
198 | @@ -46,11 +48,19 @@ |
199 | self.assertEqual(archiveroot + "-misc", primary_config.miscroot) |
200 | self.assertEqual( |
201 | self.root + "/ubuntutest-temp", primary_config.temproot) |
202 | - self.assertEqual(archiveroot + "-uefi", primary_config.uefiroot) |
203 | - self.assertFalse(primary_config.uefiautokey) |
204 | + self.assertEqual(archiveroot + "-signing", primary_config.signingroot) |
205 | + self.assertFalse(primary_config.signingautokey) |
206 | self.assertIs(None, primary_config.metaroot) |
207 | self.assertEqual(archiveroot + "-staging", primary_config.stagingroot) |
208 | |
209 | + def test_primary_config_compat(self): |
210 | + # Primary archive configuration is correct. |
211 | + archiveroot = self.root + "/ubuntutest" |
212 | + self.addCleanup(os.rmdir, archiveroot + "-uefi") |
213 | + os.makedirs(archiveroot + "-uefi") |
214 | + primary_config = getPubConfig(self.ubuntutest.main_archive) |
215 | + self.assertEqual(archiveroot + "-uefi", primary_config.signingroot) |
216 | + |
217 | def test_partner_config(self): |
218 | # Partner archive configuration is correct. |
219 | # The publisher config for PARTNER contains only 'partner' in its |
220 | @@ -70,8 +80,8 @@ |
221 | self.assertIsNone(partner_config.miscroot) |
222 | self.assertEqual( |
223 | self.root + "/ubuntutest-temp", partner_config.temproot) |
224 | - self.assertEqual(archiveroot + "-uefi", partner_config.uefiroot) |
225 | - self.assertFalse(partner_config.uefiautokey) |
226 | + self.assertEqual(archiveroot + "-signing", partner_config.signingroot) |
227 | + self.assertFalse(partner_config.signingautokey) |
228 | self.assertIs(None, partner_config.metaroot) |
229 | self.assertEqual(archiveroot + "-staging", partner_config.stagingroot) |
230 | |
231 | @@ -93,8 +103,8 @@ |
232 | self.assertEqual(archiveroot + "-cache", copy_config.cacheroot) |
233 | self.assertEqual(archiveroot + "-misc", copy_config.miscroot) |
234 | self.assertEqual(archiveroot + "-temp", copy_config.temproot) |
235 | - self.assertIsNone(copy_config.uefiroot) |
236 | - self.assertFalse(copy_config.uefiautokey) |
237 | + self.assertIsNone(copy_config.signingroot) |
238 | + self.assertFalse(copy_config.signingautokey) |
239 | self.assertIs(None, copy_config.metaroot) |
240 | self.assertIs(None, copy_config.stagingroot) |
241 | |
242 | @@ -131,10 +141,10 @@ |
243 | self.assertIsNone(self.ppa_config.miscroot) |
244 | self.assertEqual( |
245 | "/var/tmp/archive/ubuntutest-temp", self.ppa_config.temproot) |
246 | - uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( |
247 | + signingroot = "/var/tmp/ppa-signing-keys.test/signing/%s/%s" % ( |
248 | self.ppa.owner.name, self.ppa.name) |
249 | - self.assertEqual(uefiroot, self.ppa_config.uefiroot) |
250 | - self.assertTrue(self.ppa_config.uefiautokey) |
251 | + self.assertEqual(signingroot, self.ppa_config.signingroot) |
252 | + self.assertTrue(self.ppa_config.signingautokey) |
253 | self.assertIs(None, self.ppa_config.metaroot) |
254 | self.assertIs(None, self.ppa_config.stagingroot) |
255 | |
256 | @@ -166,10 +176,10 @@ |
257 | "/var/tmp/archive/ubuntutest-temp", p3a_config.temproot) |
258 | # It's OK for the signing keys to be in the same location as for |
259 | # public PPAs, as the owner/name namespace is shared. |
260 | - uefiroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( |
261 | + signingroot = "/var/tmp/ppa-signing-keys.test/signing/%s/%s" % ( |
262 | p3a.owner.name, p3a.name) |
263 | - self.assertEqual(uefiroot, p3a_config.uefiroot) |
264 | - self.assertTrue(self.ppa_config.uefiautokey) |
265 | + self.assertEqual(signingroot, p3a_config.signingroot) |
266 | + self.assertTrue(self.ppa_config.signingautokey) |
267 | self.assertIs(None, p3a_config.metaroot) |
268 | self.assertIs(None, p3a_config.stagingroot) |
269 | |
270 | @@ -186,3 +196,23 @@ |
271 | ubuntu_ppa.owner.name, ubuntu_ppa.name), |
272 | getPubConfig(ubuntu_ppa).metaroot) |
273 | self.assertIs(None, getPubConfig(test_ppa).metaroot) |
274 | + |
275 | + |
276 | +class TestGetPubConfigPPACompatUefi(TestCaseWithFactory): |
277 | + |
278 | + layer = ZopelessDatabaseLayer |
279 | + |
280 | + def setUp(self): |
281 | + super(TestGetPubConfigPPACompatUefi, self).setUp() |
282 | + self.ubuntutest = getUtility(IDistributionSet)['ubuntutest'] |
283 | + self.ppa = self.factory.makeArchive( |
284 | + distribution=self.ubuntutest, purpose=ArchivePurpose.PPA) |
285 | + signingroot = "/var/tmp/ppa-signing-keys.test/uefi" |
286 | + self.addCleanup(os.rmdir, signingroot) |
287 | + os.makedirs(signingroot) |
288 | + self.ppa_config = getPubConfig(self.ppa) |
289 | + |
290 | + def test_ppa_uefi_config(self): |
291 | + signingroot = "/var/tmp/ppa-signing-keys.test/uefi/%s/%s" % ( |
292 | + self.ppa.owner.name, self.ppa.name) |
293 | + self.assertEqual(signingroot, self.ppa_config.signingroot) |
294 | |
295 | === modified file 'lib/lp/archivepublisher/tests/test_ftparchive.py' |
296 | --- lib/lp/archivepublisher/tests/test_ftparchive.py 2016-04-05 02:07:48 +0000 |
297 | +++ lib/lp/archivepublisher/tests/test_ftparchive.py 2016-05-11 15:32:04 +0000 |
298 | @@ -552,9 +552,10 @@ |
299 | self._addRepositoryFile("main", "tiny", "tiny_0.1.tar.gz") |
300 | self._addRepositoryFile("main", "tiny", "tiny_0.1_i386.deb") |
301 | comp_dir = os.path.join(self._distsdir, "hoary-test", "main") |
302 | - os.makedirs(os.path.join(comp_dir, "uefi")) |
303 | - with open(os.path.join(comp_dir, "uefi", "stuff"), "w"): |
304 | + os.makedirs(os.path.join(comp_dir, "signed")) |
305 | + with open(os.path.join(comp_dir, "signed", "stuff"), "w"): |
306 | pass |
307 | + os.symlink("signed", os.path.join(comp_dir, "uefi")) |
308 | os.makedirs(os.path.join(comp_dir, "i18n")) |
309 | for name in ("Translation-de", "Translation-de.gz", "Translation-en", |
310 | "Translation-en.Z"): |
311 | @@ -602,6 +603,9 @@ |
312 | self.assertContentEqual( |
313 | ["Sources.gz", "Sources.xz"], |
314 | os.listdir(os.path.join(comp_dir, "source"))) |
315 | + self.assertEqual( |
316 | + ["stuff"], |
317 | + os.listdir(os.path.join(comp_dir, "signed"))) |
318 | self.assertEqual(["stuff"], os.listdir(os.path.join(comp_dir, "uefi"))) |
319 | self.assertContentEqual( |
320 | ["Translation-de", "Translation-de.gz", "Translation-en.gz", |
321 | |
322 | === renamed file 'lib/lp/archivepublisher/tests/test_uefi.py' => 'lib/lp/archivepublisher/tests/test_signing.py' |
323 | --- lib/lp/archivepublisher/tests/test_uefi.py 2016-05-03 13:35:16 +0000 |
324 | +++ lib/lp/archivepublisher/tests/test_signing.py 2016-05-11 15:32:04 +0000 |
325 | @@ -13,7 +13,7 @@ |
326 | CustomUploadAlreadyExists, |
327 | CustomUploadBadUmask, |
328 | ) |
329 | -from lp.archivepublisher.uefi import UefiUpload |
330 | +from lp.archivepublisher.signing import SigningUpload |
331 | from lp.services.osutils import write_file |
332 | from lp.services.tarfile_helpers import LaunchpadWriteTarFile |
333 | from lp.testing import TestCase |
334 | @@ -35,42 +35,42 @@ |
335 | |
336 | class FakeConfig: |
337 | """A fake publisher configuration for the main archive.""" |
338 | - def __init__(self, distroroot, uefiroot): |
339 | + def __init__(self, distroroot, signingroot): |
340 | self.distroroot = distroroot |
341 | - self.uefiroot = uefiroot |
342 | + self.signingroot = signingroot |
343 | self.archiveroot = os.path.join(self.distroroot, 'ubuntu') |
344 | - self.uefiautokey = False |
345 | + self.signingautokey = False |
346 | |
347 | |
348 | class FakeConfigPPA: |
349 | """A fake publisher configuration for a PPA.""" |
350 | - def __init__(self, distroroot, uefiroot, owner, ppa): |
351 | + def __init__(self, distroroot, signingroot, owner, ppa): |
352 | self.distroroot = distroroot |
353 | - self.uefiroot = uefiroot |
354 | + self.signingroot = signingroot |
355 | self.archiveroot = os.path.join(self.distroroot, owner, ppa, 'ubuntu') |
356 | - self.uefiautokey = True |
357 | - |
358 | - |
359 | -class TestUefi(TestCase): |
360 | + self.signingautokey = True |
361 | + |
362 | + |
363 | +class TestSigning(TestCase): |
364 | |
365 | def setUp(self): |
366 | - super(TestUefi, self).setUp() |
367 | + super(TestSigning, self).setUp() |
368 | self.temp_dir = self.makeTemporaryDirectory() |
369 | - self.uefi_dir = self.makeTemporaryDirectory() |
370 | - self.pubconf = FakeConfig(self.temp_dir, self.uefi_dir) |
371 | + self.signing_dir = self.makeTemporaryDirectory() |
372 | + self.pubconf = FakeConfig(self.temp_dir, self.signing_dir) |
373 | self.suite = "distroseries" |
374 | # CustomUpload.installFiles requires a umask of 0o022. |
375 | old_umask = os.umask(0o022) |
376 | self.addCleanup(os.umask, old_umask) |
377 | |
378 | def setUpPPA(self): |
379 | - self.pubconf = FakeConfigPPA(self.temp_dir, self.uefi_dir, |
380 | + self.pubconf = FakeConfigPPA(self.temp_dir, self.signing_dir, |
381 | 'ubuntu-archive', 'testing') |
382 | self.testcase_cn = '/CN=PPA ubuntu-archive testing/' |
383 | |
384 | def setUpKeyAndCert(self, create=True): |
385 | - self.key = os.path.join(self.uefi_dir, "uefi.key") |
386 | - self.cert = os.path.join(self.uefi_dir, "uefi.crt") |
387 | + self.key = os.path.join(self.signing_dir, "uefi.key") |
388 | + self.cert = os.path.join(self.signing_dir, "uefi.crt") |
389 | if create: |
390 | write_file(self.key, "") |
391 | write_file(self.cert, "") |
392 | @@ -85,7 +85,7 @@ |
393 | self.archive.close() |
394 | self.buffer.close() |
395 | fake_call = FakeMethod() |
396 | - upload = UefiUpload() |
397 | + upload = SigningUpload() |
398 | upload.signUefi = FakeMethod() |
399 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
400 | upload.process(self.pubconf, self.path, self.suite) |
401 | @@ -94,9 +94,16 @@ |
402 | |
403 | return upload |
404 | |
405 | + def getDistsPath(self): |
406 | + return os.path.join(self.pubconf.archiveroot, "dists", |
407 | + self.suite, "main") |
408 | + |
409 | + def getSignedPath(self, loader_type, arch): |
410 | + return os.path.join(self.getDistsPath(), "signed", |
411 | + "%s-%s" % (loader_type, arch)) |
412 | + |
413 | def getUefiPath(self, loader_type, arch): |
414 | - return os.path.join( |
415 | - self.pubconf.archiveroot, "dists", self.suite, "main", "uefi", |
416 | + return os.path.join(self.getDistsPath(), "uefi", |
417 | "%s-%s" % (loader_type, arch)) |
418 | |
419 | def test_unconfigured(self): |
420 | @@ -124,7 +131,7 @@ |
421 | self.archive.add_file("1.0/hello", "world") |
422 | upload = self.process() |
423 | self.assertTrue(os.path.exists(os.path.join( |
424 | - self.getUefiPath("empty", "amd64"), "1.0", "hello"))) |
425 | + self.getSignedPath("empty", "amd64"), "1.0", "hello"))) |
426 | self.assertEqual(0, upload.signUefi.call_count) |
427 | |
428 | def test_already_exists(self): |
429 | @@ -132,7 +139,7 @@ |
430 | self.setUpKeyAndCert() |
431 | self.openArchive("test", "1.0", "amd64") |
432 | self.archive.add_file("1.0/empty.efi", "") |
433 | - os.makedirs(os.path.join(self.getUefiPath("test", "amd64"), "1.0")) |
434 | + os.makedirs(os.path.join(self.getSignedPath("test", "amd64"), "1.0")) |
435 | self.assertRaises(CustomUploadAlreadyExists, self.process) |
436 | |
437 | def test_bad_umask(self): |
438 | @@ -149,7 +156,7 @@ |
439 | self.setUpKeyAndCert() |
440 | fake_call = FakeMethod() |
441 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
442 | - upload = UefiUpload() |
443 | + upload = SigningUpload() |
444 | upload.generateUefiKeys = FakeMethod() |
445 | upload.setTargetDirectory( |
446 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
447 | @@ -169,7 +176,7 @@ |
448 | self.setUpKeyAndCert(create=False) |
449 | fake_call = FakeMethod() |
450 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
451 | - upload = UefiUpload() |
452 | + upload = SigningUpload() |
453 | upload.generateUefiKeys = FakeMethod() |
454 | upload.setTargetDirectory( |
455 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
456 | @@ -184,7 +191,7 @@ |
457 | self.setUpKeyAndCert(create=False) |
458 | fake_call = FakeMethod() |
459 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
460 | - upload = UefiUpload() |
461 | + upload = SigningUpload() |
462 | upload.setTargetDirectory( |
463 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
464 | upload.generateUefiKeys() |
465 | @@ -212,6 +219,44 @@ |
466 | self.openArchive("test", "1.0", "amd64") |
467 | self.archive.add_file("1.0/empty.efi", "") |
468 | self.process() |
469 | + self.assertTrue(os.path.isdir(os.path.join( |
470 | + self.getDistsPath(), "signed"))) |
471 | + self.assertTrue(os.path.islink(os.path.join( |
472 | + self.getDistsPath(), "uefi"))) |
473 | + self.assertTrue(os.path.exists(os.path.join( |
474 | + self.getSignedPath("test", "amd64"), "1.0", "empty.efi"))) |
475 | + self.assertTrue(os.path.exists(os.path.join( |
476 | + self.getUefiPath("test", "amd64"), "1.0", "empty.efi"))) |
477 | + |
478 | + def test_installed_existing_uefi(self): |
479 | + # Files in the tarball are installed correctly. |
480 | + os.makedirs(os.path.join(self.getDistsPath(), "uefi")) |
481 | + self.setUpKeyAndCert() |
482 | + self.openArchive("test", "1.0", "amd64") |
483 | + self.archive.add_file("1.0/empty.efi", "") |
484 | + self.process() |
485 | + self.assertTrue(os.path.isdir(os.path.join( |
486 | + self.getDistsPath(), "signed"))) |
487 | + self.assertTrue(os.path.islink(os.path.join( |
488 | + self.getDistsPath(), "uefi"))) |
489 | + self.assertTrue(os.path.exists(os.path.join( |
490 | + self.getSignedPath("test", "amd64"), "1.0", "empty.efi"))) |
491 | + self.assertTrue(os.path.exists(os.path.join( |
492 | + self.getUefiPath("test", "amd64"), "1.0", "empty.efi"))) |
493 | + |
494 | + def test_installed_existing_signing(self): |
495 | + # Files in the tarball are installed correctly. |
496 | + os.makedirs(os.path.join(self.getDistsPath(), "signing")) |
497 | + self.setUpKeyAndCert() |
498 | + self.openArchive("test", "1.0", "amd64") |
499 | + self.archive.add_file("1.0/empty.efi", "") |
500 | + self.process() |
501 | + self.assertTrue(os.path.isdir(os.path.join( |
502 | + self.getDistsPath(), "signed"))) |
503 | + self.assertTrue(os.path.islink(os.path.join( |
504 | + self.getDistsPath(), "uefi"))) |
505 | + self.assertTrue(os.path.exists(os.path.join( |
506 | + self.getSignedPath("test", "amd64"), "1.0", "empty.efi"))) |
507 | self.assertTrue(os.path.exists(os.path.join( |
508 | self.getUefiPath("test", "amd64"), "1.0", "empty.efi"))) |
509 | |
510 | @@ -222,7 +267,7 @@ |
511 | self.assertFalse(os.path.exists(self.cert)) |
512 | fake_call = FakeMethod() |
513 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
514 | - upload = UefiUpload() |
515 | + upload = SigningUpload() |
516 | upload.generateUefiKeys = FakeMethodGenUefiKeys(upload=upload) |
517 | upload.setTargetDirectory( |
518 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
519 | @@ -239,7 +284,7 @@ |
520 | self.assertFalse(os.path.exists(self.cert)) |
521 | fake_call = FakeMethod() |
522 | self.useFixture(MonkeyPatch("subprocess.call", fake_call)) |
523 | - upload = UefiUpload() |
524 | + upload = SigningUpload() |
525 | upload.generateUefiKeys = FakeMethodGenUefiKeys(upload=upload) |
526 | upload.setTargetDirectory( |
527 | self.pubconf, "test_1.0_amd64.tar.gz", "distroseries") |
528 | |
529 | === modified file 'lib/lp/archiveuploader/nascentuploadfile.py' |
530 | --- lib/lp/archiveuploader/nascentuploadfile.py 2015-01-28 17:54:34 +0000 |
531 | +++ lib/lp/archiveuploader/nascentuploadfile.py 2016-05-11 15:32:04 +0000 |
532 | @@ -1,4 +1,4 @@ |
533 | -# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
534 | +# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
535 | # GNU Affero General Public License version 3 (see the file LICENSE). |
536 | |
537 | """Specific models for uploaded files""" |
538 | @@ -33,7 +33,7 @@ |
539 | from lp.archivepublisher.debian_installer import DebianInstallerUpload |
540 | from lp.archivepublisher.dist_upgrader import DistUpgraderUpload |
541 | from lp.archivepublisher.rosetta_translations import RosettaTranslationsUpload |
542 | -from lp.archivepublisher.uefi import UefiUpload |
543 | +from lp.archivepublisher.signing import SigningUpload |
544 | from lp.archiveuploader.utils import ( |
545 | determine_source_file_type, |
546 | prefix_multi_line_string, |
547 | @@ -258,7 +258,8 @@ |
548 | PackageUploadCustomFormat.STATIC_TRANSLATIONS, |
549 | 'raw-meta-data': |
550 | PackageUploadCustomFormat.META_DATA, |
551 | - 'raw-uefi': PackageUploadCustomFormat.UEFI, |
552 | + 'raw-signing': PackageUploadCustomFormat.SIGNING, |
553 | + 'raw-uefi': PackageUploadCustomFormat.SIGNING, # Compatibility |
554 | } |
555 | |
556 | custom_handlers = { |
557 | @@ -267,7 +268,7 @@ |
558 | PackageUploadCustomFormat.DDTP_TARBALL: DdtpTarballUpload, |
559 | PackageUploadCustomFormat.ROSETTA_TRANSLATIONS: |
560 | RosettaTranslationsUpload, |
561 | - PackageUploadCustomFormat.UEFI: UefiUpload, |
562 | + PackageUploadCustomFormat.SIGNING: SigningUpload, |
563 | } |
564 | |
565 | @property |
566 | @@ -306,8 +307,9 @@ |
567 | |
568 | def autoApprove(self): |
569 | """Return whether this custom upload can be automatically approved.""" |
570 | - # UEFI uploads are signed, and must therefore be approved by a human. |
571 | - if self.custom_type == PackageUploadCustomFormat.UEFI: |
572 | + # Signing uploads will be signed, and must therefore be approved |
573 | + # by a human. |
574 | + if self.custom_type == PackageUploadCustomFormat.SIGNING: |
575 | return False |
576 | return True |
577 | |
578 | |
579 | === modified file 'lib/lp/archiveuploader/tests/test_nascentuploadfile.py' |
580 | --- lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2015-03-04 13:06:02 +0000 |
581 | +++ lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2016-05-11 15:32:04 +0000 |
582 | @@ -1,4 +1,4 @@ |
583 | -# Copyright 2010-2015 Canonical Ltd. This software is licensed under the |
584 | +# Copyright 2010-2016 Canonical Ltd. This software is licensed under the |
585 | # GNU Affero General Public License version 3 (see the file LICENSE). |
586 | |
587 | """Test NascentUploadFile functionality.""" |
588 | @@ -167,6 +167,12 @@ |
589 | "bla.txt", "data", "main/raw-uefi", "extra") |
590 | self.assertFalse(uploadfile.autoApprove()) |
591 | |
592 | + def test_signing_not_auto_approved(self): |
593 | + # UEFI uploads are auto-approved. |
594 | + uploadfile = self.createCustomUploadFile( |
595 | + "bla.txt", "data", "main/raw-signing", "extra") |
596 | + self.assertFalse(uploadfile.autoApprove()) |
597 | + |
598 | |
599 | class PackageUploadFileTestCase(NascentUploadFileTestCase): |
600 | """Base class for all tests of classes deriving from PackageUploadFile.""" |
601 | |
602 | === modified file 'lib/lp/archiveuploader/tests/test_uploadpolicy.py' |
603 | --- lib/lp/archiveuploader/tests/test_uploadpolicy.py 2013-08-01 14:09:45 +0000 |
604 | +++ lib/lp/archiveuploader/tests/test_uploadpolicy.py 2016-05-11 15:32:04 +0000 |
605 | @@ -1,6 +1,6 @@ |
606 | #!/usr/bin/python |
607 | # |
608 | -# Copyright 2010-2013 Canonical Ltd. This software is licensed under the |
609 | +# Copyright 2010-2016 Canonical Ltd. This software is licensed under the |
610 | # GNU Affero General Public License version 3 (see the file LICENSE). |
611 | |
612 | from zope.component import getUtility |
613 | @@ -304,6 +304,17 @@ |
614 | upload.changes = FakeChangesFile(custom_files=[uploadfile]) |
615 | self.assertFalse(buildd_policy.autoApprove(upload)) |
616 | |
617 | + def test_buildd_does_not_approve_signing(self): |
618 | + # Uploads to the primary archive containing files for signing are |
619 | + # not approved. |
620 | + buildd_policy = findPolicyByName("buildd") |
621 | + uploadfile = CustomUploadFile( |
622 | + "uefi.tar.gz", None, 0, "main/raw-signing", "extra", buildd_policy, |
623 | + None) |
624 | + upload = make_fake_upload(binaryful=True) |
625 | + upload.changes = FakeChangesFile(custom_files=[uploadfile]) |
626 | + self.assertFalse(buildd_policy.autoApprove(upload)) |
627 | + |
628 | def test_buildd_approves_uefi_ppa(self): |
629 | # Uploads to PPAs containing UEFI custom files are auto-approved. |
630 | buildd_policy = findPolicyByName("buildd") |
631 | @@ -313,3 +324,13 @@ |
632 | upload = make_fake_upload(binaryful=True, is_ppa=True) |
633 | upload.changes = FakeChangesFile(custom_files=[uploadfile]) |
634 | self.assertTrue(buildd_policy.autoApprove(upload)) |
635 | + |
636 | + def test_buildd_approves_signing_ppa(self): |
637 | + # Uploads to PPAs containing UEFI custom files are auto-approved. |
638 | + buildd_policy = findPolicyByName("buildd") |
639 | + uploadfile = CustomUploadFile( |
640 | + "uefi.tar.gz", None, 0, "main/raw-signing", "extra", buildd_policy, |
641 | + None) |
642 | + upload = make_fake_upload(binaryful=True, is_ppa=True) |
643 | + upload.changes = FakeChangesFile(custom_files=[uploadfile]) |
644 | + self.assertTrue(buildd_policy.autoApprove(upload)) |
645 | |
646 | === modified file 'lib/lp/soyuz/browser/queue.py' |
647 | --- lib/lp/soyuz/browser/queue.py 2015-07-09 20:06:17 +0000 |
648 | +++ lib/lp/soyuz/browser/queue.py 2016-05-11 15:32:04 +0000 |
649 | @@ -1,4 +1,4 @@ |
650 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
651 | +# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
652 | # GNU Affero General Public License version 3 (see the file LICENSE). |
653 | |
654 | """Browser views for package queue.""" |
655 | @@ -574,7 +574,7 @@ |
656 | (self.contains_installer, ("Installer", 'ubuntu-icon')), |
657 | (self.contains_upgrader, ("Upgrader", 'ubuntu-icon')), |
658 | (self.contains_ddtp, (ddtp, 'ubuntu-icon')), |
659 | - (self.contains_uefi, ("Signed UEFI boot loader", 'ubuntu-icon')), |
660 | + (self.contains_signing, ("Objects for Signing", 'ubuntu-icon')), |
661 | ] |
662 | return [ |
663 | self.composeIcon(*details) |
664 | |
665 | === modified file 'lib/lp/soyuz/configure.zcml' |
666 | --- lib/lp/soyuz/configure.zcml 2016-03-22 12:51:03 +0000 |
667 | +++ lib/lp/soyuz/configure.zcml 2016-05-11 15:32:04 +0000 |
668 | @@ -168,6 +168,7 @@ |
669 | contains_installer |
670 | contains_upgrader |
671 | contains_ddtp |
672 | + contains_signing |
673 | contains_uefi |
674 | displayname |
675 | displayarchs |
676 | |
677 | === modified file 'lib/lp/soyuz/enums.py' |
678 | --- lib/lp/soyuz/enums.py 2016-02-05 20:28:29 +0000 |
679 | +++ lib/lp/soyuz/enums.py 2016-05-11 15:32:04 +0000 |
680 | @@ -481,10 +481,10 @@ |
681 | the Software Center. |
682 | """) |
683 | |
684 | - UEFI = DBItem(6, """ |
685 | - uefi |
686 | + SIGNING = DBItem(6, """ |
687 | + signing |
688 | |
689 | - A UEFI boot loader image to be signed. |
690 | + A tarball containing images to be signed. |
691 | """) |
692 | |
693 | |
694 | |
695 | === modified file 'lib/lp/soyuz/interfaces/queue.py' |
696 | --- lib/lp/soyuz/interfaces/queue.py 2015-09-03 15:14:07 +0000 |
697 | +++ lib/lp/soyuz/interfaces/queue.py 2016-05-11 15:32:04 +0000 |
698 | @@ -1,4 +1,4 @@ |
699 | -# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
700 | +# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
701 | # GNU Affero General Public License version 3 (see the file LICENSE). |
702 | |
703 | """Queue interfaces.""" |
704 | @@ -264,8 +264,11 @@ |
705 | "whether or not this upload contains upgrader images") |
706 | contains_ddtp = Attribute( |
707 | "whether or not this upload contains DDTP images") |
708 | + contains_signing = Attribute( |
709 | + "whether or not this upload contains signing images") |
710 | contains_uefi = Attribute( |
711 | - "whether or not this upload contains a signed UEFI boot loader image") |
712 | + "whether or not this upload contains a signed UEFI boot loader image" |
713 | + " (deprecated)") |
714 | isPPA = Attribute( |
715 | "Return True if this PackageUpload is a PPA upload.") |
716 | |
717 | |
718 | === modified file 'lib/lp/soyuz/model/queue.py' |
719 | --- lib/lp/soyuz/model/queue.py 2016-03-14 23:42:45 +0000 |
720 | +++ lib/lp/soyuz/model/queue.py 2016-05-11 15:32:04 +0000 |
721 | @@ -665,9 +665,11 @@ |
722 | return PackageUploadCustomFormat.DDTP_TARBALL in self._customFormats |
723 | |
724 | @cachedproperty |
725 | - def contains_uefi(self): |
726 | + def contains_signing(self): |
727 | """See `IPackageUpload`.""" |
728 | - return PackageUploadCustomFormat.UEFI in self._customFormats |
729 | + return PackageUploadCustomFormat.SIGNING in self._customFormats |
730 | + |
731 | + contains_uefi = contains_signing |
732 | |
733 | @property |
734 | def package_name(self): |
735 | @@ -1462,13 +1464,13 @@ |
736 | self.libraryfilealias.open() |
737 | copy_and_close(self.libraryfilealias, file_obj) |
738 | |
739 | - def publishUefi(self, logger=None): |
740 | + def publishSigning(self, logger=None): |
741 | """See `IPackageUploadCustom`.""" |
742 | # XXX cprov 2005-03-03: We need to use the Zope Component Lookup |
743 | # to instantiate the object in question and avoid circular imports |
744 | - from lp.archivepublisher.uefi import process_uefi |
745 | + from lp.archivepublisher.signing import process_signing |
746 | |
747 | - self._publishCustom(process_uefi, logger=logger) |
748 | + self._publishCustom(process_signing, logger=logger) |
749 | |
750 | publisher_dispatch = { |
751 | PackageUploadCustomFormat.DEBIAN_INSTALLER: publishDebianInstaller, |
752 | @@ -1479,7 +1481,7 @@ |
753 | PackageUploadCustomFormat.STATIC_TRANSLATIONS: |
754 | publishStaticTranslations, |
755 | PackageUploadCustomFormat.META_DATA: publishMetaData, |
756 | - PackageUploadCustomFormat.UEFI: publishUefi, |
757 | + PackageUploadCustomFormat.SIGNING: publishSigning, |
758 | } |
759 | |
760 | # publisher_dispatch must have an entry for each value of |
761 | |
762 | === modified file 'lib/lp/soyuz/scripts/custom_uploads_copier.py' |
763 | --- lib/lp/soyuz/scripts/custom_uploads_copier.py 2013-10-10 18:47:16 +0000 |
764 | +++ lib/lp/soyuz/scripts/custom_uploads_copier.py 2016-05-11 15:32:04 +0000 |
765 | @@ -1,4 +1,4 @@ |
766 | -# Copyright 2011-2013 Canonical Ltd. This software is licensed under the |
767 | +# Copyright 2011-2016 Canonical Ltd. This software is licensed under the |
768 | # GNU Affero General Public License version 3 (see the file LICENSE). |
769 | |
770 | """Copy latest custom uploads into a distribution release series. |
771 | @@ -18,7 +18,7 @@ |
772 | from lp.archivepublisher.debian_installer import DebianInstallerUpload |
773 | from lp.archivepublisher.dist_upgrader import DistUpgraderUpload |
774 | from lp.archivepublisher.rosetta_translations import RosettaTranslationsUpload |
775 | -from lp.archivepublisher.uefi import UefiUpload |
776 | +from lp.archivepublisher.signing import SigningUpload |
777 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
778 | from lp.services.database.bulk import load_referencing |
779 | from lp.soyuz.enums import PackageUploadCustomFormat |
780 | @@ -40,7 +40,7 @@ |
781 | PackageUploadCustomFormat.DDTP_TARBALL: DdtpTarballUpload, |
782 | PackageUploadCustomFormat.ROSETTA_TRANSLATIONS: |
783 | RosettaTranslationsUpload, |
784 | - PackageUploadCustomFormat.UEFI: UefiUpload, |
785 | + PackageUploadCustomFormat.SIGNING: SigningUpload, |
786 | } |
787 | |
788 | def __init__(self, target_series, |
789 | @@ -61,8 +61,9 @@ |
790 | if (custom.packageupload.archive.is_ppa or |
791 | custom.packageupload.archive == source_archive): |
792 | return True |
793 | - # UEFI uploads are signed, and must therefore be approved by a human. |
794 | - if custom.customformat == PackageUploadCustomFormat.UEFI: |
795 | + # Signing uploads will be signed, and must therefore be approved |
796 | + # by a human. |
797 | + if custom.customformat == PackageUploadCustomFormat.SIGNING: |
798 | return False |
799 | return True |
800 | |
801 | |
802 | === modified file 'lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py' |
803 | --- lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2013-10-10 18:47:16 +0000 |
804 | +++ lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2016-05-11 15:32:04 +0000 |
805 | @@ -1,4 +1,4 @@ |
806 | -# Copyright 2011-2013 Canonical Ltd. This software is licensed under the |
807 | +# Copyright 2011-2016 Canonical Ltd. This software is licensed under the |
808 | # GNU Affero General Public License version 3 (see the file LICENSE). |
809 | |
810 | """Test copying of custom package uploads for a new `DistroSeries`.""" |
811 | @@ -408,36 +408,36 @@ |
812 | copied_pu = copier.copyUpload(original_upload).packageupload |
813 | self.assertEqual(PackageUploadStatus.ACCEPTED, copied_pu.status) |
814 | |
815 | - def test_copyUpload_unapproves_uefi_from_different_archive(self): |
816 | + def test_copyUpload_unapproves_signing_from_different_archive(self): |
817 | # Copies of UEFI custom uploads to a primary archive are set to |
818 | # UNAPPROVED, since they will normally end up being signed. |
819 | target_series = self.factory.makeDistroSeries() |
820 | archive = self.factory.makeArchive( |
821 | distribution=target_series.distribution) |
822 | original_upload = self.makeUpload( |
823 | - archive=archive, custom_type=PackageUploadCustomFormat.UEFI) |
824 | + archive=archive, custom_type=PackageUploadCustomFormat.SIGNING) |
825 | copier = CustomUploadsCopier( |
826 | target_series, target_archive=target_series.main_archive) |
827 | copied_pu = copier.copyUpload(original_upload).packageupload |
828 | self.assertEqual(PackageUploadStatus.UNAPPROVED, copied_pu.status) |
829 | |
830 | - def test_copyUpload_approves_uefi_from_same_archive(self): |
831 | + def test_copyUpload_approves_signing_from_same_archive(self): |
832 | # Copies of UEFI custom uploads within the same archive are |
833 | # automatically accepted, since they have already been signed. |
834 | original_upload = self.makeUpload( |
835 | - custom_type=PackageUploadCustomFormat.UEFI) |
836 | + custom_type=PackageUploadCustomFormat.SIGNING) |
837 | target_series = self.factory.makeDistroSeries() |
838 | copier = CustomUploadsCopier(target_series) |
839 | copied_pu = copier.copyUpload(original_upload).packageupload |
840 | self.assertEqual(PackageUploadStatus.ACCEPTED, copied_pu.status) |
841 | |
842 | - def test_copyUpload_approves_uefi_to_ppa(self): |
843 | + def test_copyUpload_approves_signing_to_ppa(self): |
844 | # Copies of UEFI custom uploads to a PPA are automatically accepted, |
845 | # since PPAs have much more limited upload permissions than the main |
846 | # archive, and in any case PPAs do not have an upload approval |
847 | # workflow. |
848 | original_upload = self.makeUpload( |
849 | - custom_type=PackageUploadCustomFormat.UEFI) |
850 | + custom_type=PackageUploadCustomFormat.SIGNING) |
851 | target_series = self.factory.makeDistroSeries() |
852 | target_archive = self.factory.makeArchive( |
853 | distribution=target_series.distribution) |
Ok, I have pushed up a further set of changes. These include making the signing dists directory primary and adding tests for it actually being linked correctly. I have also addresed the naming issues you pointed out.