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