launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #09111
[Merge] lp:~cjwatson/launchpad/custom-uefi into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/custom-uefi into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1016594 in Launchpad itself: "UEFI image signing"
https://bugs.launchpad.net/launchpad/+bug/1016594
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/custom-uefi/+merge/111626
== Summary ==
Bug 1016594: Ubuntu Engineering needs Launchpad to support signing UEFI boot loaders and publishing them in the Ubuntu archive.
== Proposed fix ==
Create a new custom upload format for UEFI. This provides a reasonable place for us to sign images on publication.
Make the key/certificate location configurable in launchpad-lazr.conf, for fairly obvious reasons. Filesystem locations may differ, we're only going to want the real key on production, and developer machines don't need a key at all.
Never auto-approve uploads containing UEFI files for signing, since signed images are going to be rather high-value. This means that we don't have to invent some new permissioning system for who's allowed to upload UEFI images or which packages they're allowed to go in; instead, we can just rely on them all being held for manual approval.
== LOC Rationale ==
+311. This feature is critical to UE, and I have 3039 lines of credit right now, so I'd very much like to use some of that. Most of my other work at the moment is coming out LoC-negative.
== Tests ==
bin/test -vvct test_uefi -t test_ftparchive -t test_generate_extra_overrides -t test_nascentuploadfile -t test_custom_uploads_copier
== Demo and Q/A ==
Once sbsigntool has been installed on dogfood along with a test key, we should change efilinux to add a raw-uefi custom tarball and upload that to dogfood. It should be extracted to /dists/quantal/main/uefi/efilinux-<ARCH>/<VERSION>/ and the .efi file signed by the test key, and a current -> <VERSION> symlink should be created.
== Lint ==
Pre-existing lint in *-lazr.conf files, which I don't think I should touch:
./configs/development/launchpad-lazr.conf
64: Line exceeds 80 characters.
93: Line exceeds 80 characters.
./lib/lp/services/config/schema-lazr.conf
457: Line exceeds 80 characters.
1050: Line exceeds 80 characters.
1057: Line exceeds 80 characters.
1595: Line exceeds 80 characters.
--
https://code.launchpad.net/~cjwatson/launchpad/custom-uefi/+merge/111626
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/custom-uefi into lp:launchpad.
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf 2012-05-24 01:43:42 +0000
+++ configs/development/launchpad-lazr.conf 2012-06-22 15:32:21 +0000
@@ -7,6 +7,8 @@
[archivepublisher]
run_parts_location: none
+uefi_key_location: none
+uefi_cert_location: none
[builddmaster]
root: /var/tmp/builddmaster/
=== modified file 'lib/lp/archivepublisher/model/ftparchive.py'
--- lib/lp/archivepublisher/model/ftparchive.py 2012-03-27 11:08:12 +0000
+++ lib/lp/archivepublisher/model/ftparchive.py 2012-06-22 15:32:21 +0000
@@ -21,6 +21,7 @@
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.stormexpr import Concatenate
from lp.services.librarian.model import LibraryFileAlias
+from lp.services.osutils import write_file
from lp.services.webapp.interfaces import (
DEFAULT_FLAVOR,
IStoreSelector,
@@ -42,12 +43,6 @@
return (os.path.basename(filename).split("_"))[0]
-def f_touch(*parts):
- """Touch the file named by the arguments concatenated as a path."""
- fname = os.path.join(*parts)
- open(fname, "w").close()
-
-
def safe_mkdir(path):
"""Ensures the path exists, creating it if it doesn't."""
if not os.path.exists(path):
@@ -215,15 +210,15 @@
(comp, "debian-installer"),
(comp, "src"),
):
- f_touch(os.path.join(
+ write_file(os.path.join(
self._config.overrideroot,
- ".".join(("override", suite) + path)))
+ ".".join(("override", suite) + path)), "")
# Create empty file lists.
def touch_list(*parts):
- f_touch(os.path.join(
+ write_file(os.path.join(
self._config.overrideroot,
- "_".join((suite, ) + parts)))
+ "_".join((suite, ) + parts)), "")
touch_list(comp, "source")
arch_tags = [
=== modified file 'lib/lp/archivepublisher/tests/test_ftparchive.py'
--- lib/lp/archivepublisher/tests/test_ftparchive.py 2012-03-27 12:07:15 +0000
+++ lib/lp/archivepublisher/tests/test_ftparchive.py 2012-06-22 15:32:21 +0000
@@ -16,7 +16,6 @@
from lp.archivepublisher.diskpool import DiskPool
from lp.archivepublisher.model.ftparchive import (
AptFTPArchiveFailure,
- f_touch,
FTPArchiveHandler,
)
from lp.archivepublisher.publishing import Publisher
@@ -27,10 +26,7 @@
BufferLogger,
DevNullLogger,
)
-from lp.testing import (
- TestCase,
- TestCaseWithFactory,
- )
+from lp.testing import TestCaseWithFactory
from lp.testing.dbuser import switch_dbuser
from lp.testing.layers import (
LaunchpadZopelessLayer,
@@ -493,26 +489,3 @@
distro = distroarchseries.distroseries.distribution
fa = FTPArchiveHandler(DevNullLogger(), None, None, distro, None)
self.assertRaises(AptFTPArchiveFailure, fa.runApt, "bogus-config")
-
-
-class TestFTouch(TestCase):
- """Tests for f_touch function."""
-
- def setUp(self):
- TestCase.setUp(self)
- self.test_folder = self.useTempDir()
-
- def test_f_touch_new_file(self):
- # Test f_touch correctly creates a new file.
- f_touch(self.test_folder, "file_to_touch")
- self.assertTrue(os.path.exists("%s/file_to_touch" % self.test_folder))
-
- def test_f_touch_existing_file(self):
- # Test f_touch truncates existing files.
- with open("%s/file_to_truncate" % self.test_folder, "w") as f:
- f.write("I'm some test contents")
-
- f_touch(self.test_folder, "file_to_leave_alone")
-
- with open("%s/file_to_leave_alone" % self.test_folder, "r") as f:
- self.assertEqual("", f.read())
=== modified file 'lib/lp/archivepublisher/tests/test_generate_extra_overrides.py'
--- lib/lp/archivepublisher/tests/test_generate_extra_overrides.py 2012-05-21 19:03:16 +0000
+++ lib/lp/archivepublisher/tests/test_generate_extra_overrides.py 2012-06-22 15:32:21 +0000
@@ -29,6 +29,7 @@
from lp.services.osutils import (
ensure_directory_exists,
open_for_writing,
+ write_file,
)
from lp.services.scripts.base import LaunchpadScriptFailure
from lp.services.utils import file_exists
@@ -47,12 +48,6 @@
return handle.read()
-def touch(path):
- """Create an empty file at path."""
- with open_for_writing(path, "a"):
- pass
-
-
class TestAtomicFile(TestCaseWithFactory):
"""Tests for the AtomicFile helper class."""
@@ -562,7 +557,7 @@
other_file = "other-file"
output = partial(os.path.join, self.script.config.germinateroot)
for base in (seed_old_file, seed_new_file, other_file):
- touch(output(base))
+ write_file(output(base), "")
self.script.removeStaleOutputs(series_name, set([seed_new_file]))
self.assertFalse(os.path.exists(output(seed_old_file)))
self.assertTrue(os.path.exists(output(seed_new_file)))
=== added file 'lib/lp/archivepublisher/tests/test_uefi.py'
--- lib/lp/archivepublisher/tests/test_uefi.py 1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/tests/test_uefi.py 2012-06-22 15:32:21 +0000
@@ -0,0 +1,137 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test UEFI custom uploads."""
+
+import os
+from textwrap import dedent
+
+from lp.archivepublisher.customupload import (
+ CustomUploadAlreadyExists,
+ CustomUploadBadUmask,
+ )
+from lp.archivepublisher.uefi import (
+ UefiConfigurationError,
+ UefiNothingToSign,
+ UefiUpload,
+ )
+from lp.services.config import config
+from lp.services.osutils import write_file
+from lp.services.tarfile_helpers import LaunchpadWriteTarFile
+from lp.testing import TestCase
+from lp.testing.fakemethod import FakeMethod
+
+
+class TestUefi(TestCase):
+
+ def setUp(self):
+ super(TestUefi, self).setUp()
+ self.temp_dir = self.makeTemporaryDirectory()
+ self.suite = "distroseries"
+ # CustomUpload.installFiles requires a umask of 022.
+ old_umask = os.umask(022)
+ self.addCleanup(os.umask, old_umask)
+
+ def pushConfiguration(self, key_location, cert_location):
+ uefi_config = dedent("""
+ [archivepublisher]
+ uefi_key_location: %s
+ uefi_cert_location: %s
+ """ % (key_location, cert_location))
+ config.push("uefi_config", uefi_config)
+ self.addCleanup(config.pop, "uefi_config")
+
+ def setUpKeyAndCert(self):
+ self.key_location = os.path.join(self.temp_dir, "test.key")
+ self.cert_location = os.path.join(self.temp_dir, "test.cert")
+ write_file(self.key_location, "")
+ write_file(self.cert_location, "")
+ self.pushConfiguration(self.key_location, self.cert_location)
+
+ def openArchive(self, loader_type, version, arch):
+ self.path = os.path.join(
+ self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch))
+ self.buffer = open(self.path, "wb")
+ self.archive = LaunchpadWriteTarFile(self.buffer)
+
+ def process(self):
+ self.archive.close()
+ self.buffer.close()
+ upload = UefiUpload()
+ upload.sign = FakeMethod()
+ upload.process(self.temp_dir, self.path, self.suite)
+ return upload
+
+ def getUefiPath(self, loader_type, arch):
+ return os.path.join(
+ self.temp_dir, "dists", self.suite, "main", "uefi",
+ "%s-%s" % (loader_type, arch))
+
+ def test_unconfigured(self):
+ # If there is no key/cert configuration, processing fails.
+ self.pushConfiguration("none", "none")
+ self.openArchive("test", "1.0", "amd64")
+ self.assertRaises(UefiConfigurationError, self.process)
+
+ def test_missing_key_and_cert(self):
+ # If the configured key/cert are missing, processing fails.
+ self.pushConfiguration(
+ os.path.join(self.temp_dir, "key"),
+ os.path.join(self.temp_dir, "cert"))
+ self.openArchive("test", "1.0", "amd64")
+ self.archive.add_file("1.0/empty.efi", "")
+ self.assertRaises(UefiConfigurationError, self.process)
+
+ def test_no_efi_files(self):
+ # Tarballs containing no *.efi files are rejected.
+ self.setUpKeyAndCert()
+ self.openArchive("empty", "1.0", "amd64")
+ self.archive.add_file("hello", "world")
+ self.assertRaises(UefiNothingToSign, self.process)
+
+ def test_already_exists(self):
+ # If the target directory already exists, processing fails.
+ self.setUpKeyAndCert()
+ self.openArchive("test", "1.0", "amd64")
+ self.archive.add_file("1.0/empty.efi", "")
+ os.makedirs(os.path.join(self.getUefiPath("test", "amd64"), "1.0"))
+ self.assertRaises(CustomUploadAlreadyExists, self.process)
+
+ def test_bad_umask(self):
+ # The umask must be 022 to avoid incorrect permissions.
+ self.setUpKeyAndCert()
+ self.openArchive("test", "1.0", "amd64")
+ self.archive.add_file("1.0/dir/file.efi", "foo")
+ os.umask(002) # cleanup already handled by setUp
+ self.assertRaises(CustomUploadBadUmask, self.process)
+
+ def test_correct_signing_command(self):
+ # getSigningCommand returns the correct command.
+ self.setUpKeyAndCert()
+ upload = UefiUpload()
+ upload.setTargetDirectory(
+ self.temp_dir, "test_1.0_amd64.tar.gz", "distroseries")
+ expected_command = [
+ "sbsign", "--key", self.key_location, "--cert", self.cert_location,
+ "t.efi"]
+ self.assertEqual(expected_command, upload.getSigningCommand("t.efi"))
+
+ def test_signs_image(self):
+ # Each image in the tarball is signed.
+ self.setUpKeyAndCert()
+ self.openArchive("test", "1.0", "amd64")
+ self.archive.add_file("1.0/empty.efi", "")
+ upload = self.process()
+ self.assertEqual(1, upload.sign.call_count)
+ self.assertEqual(1, len(upload.sign.calls[0][0]))
+ self.assertEqual(
+ "empty.efi", os.path.basename(upload.sign.calls[0][0][0]))
+
+ def test_installed(self):
+ # Files in the tarball are installed correctly.
+ self.setUpKeyAndCert()
+ self.openArchive("test", "1.0", "amd64")
+ self.archive.add_file("1.0/empty.efi", "")
+ self.process()
+ self.assertTrue(os.path.exists(os.path.join(
+ self.getUefiPath("test", "amd64"), "1.0", "empty.efi")))
=== added file 'lib/lp/archivepublisher/uefi.py'
--- lib/lp/archivepublisher/uefi.py 1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/uefi.py 2012-06-22 15:32:21 +0000
@@ -0,0 +1,142 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""The processing of UEFI boot loader images.
+
+UEFI Secure Boot requires boot loader images to be signed, and we want to
+have signed images in the archive so that they can be used for upgrades.
+This cannot be done on the build daemons because they are insufficiently
+secure to hold signing keys, so we sign them as a custom upload instead.
+"""
+
+__metaclass__ = type
+
+__all__ = [
+ "UefiUpload",
+ "process_uefi",
+ ]
+
+import os
+import subprocess
+
+from lp.archivepublisher.customupload import (
+ CustomUpload,
+ CustomUploadError,
+ )
+from lp.services.config import config
+from lp.services.osutils import remove_if_exists
+
+
+class UefiConfigurationError(CustomUploadError):
+ """No signing key location is configured."""
+ def __init__(self, message):
+ CustomUploadError.__init__(
+ self, "UEFI signing configuration error: %s" % message)
+
+
+class UefiNothingToSign(CustomUploadError):
+ """The tarball contained no *.efi files."""
+ def __init__(self, tarfile_path):
+ CustomUploadError.__init__(
+ self, "UEFI upload '%s' contained no *.efi files" % tarfile_path)
+
+
+class UefiUpload(CustomUpload):
+ """UEFI boot loader custom upload.
+
+ The filename should be something like:
+
+ <TYPE>_<VERSION>_<ARCH>.tar.gz
+
+ where:
+
+ * TYPE: loader type (e.g. 'efilinux');
+ * VERSION: encoded version;
+ * ARCH: targeted architecture tag (e.g. 'amd64').
+
+ The contents are extracted in the archive in the following path:
+
+ <ARCHIVE>/dists/<SUITE>/main/uefi/<TYPE>-<ARCH>/<VERSION>
+
+ A 'current' symbolic link points to the most recent version. The
+ tarfile must contain at least one file matching the wildcard *.efi, and
+ any such files are signed using the key configured in
+ config.archivepublisher.uefi_key_location.
+ """
+ custom_type = "UEFI"
+
+ def setTargetDirectory(self, archive_root, tarfile_path, distroseries):
+ self.uefi_key_location = config.archivepublisher.uefi_key_location
+ self.uefi_cert_location = config.archivepublisher.uefi_cert_location
+ if self.uefi_key_location is None:
+ raise UefiConfigurationError("no key configured")
+ if not os.access(self.uefi_key_location, os.R_OK):
+ raise UefiConfigurationError(
+ "configured key %s not readable" % self.uefi_key_location)
+ if self.uefi_cert_location is None:
+ raise UefiConfigurationError("no certificate configured")
+ if not os.access(self.uefi_cert_location, os.R_OK):
+ raise UefiConfigurationError(
+ "configured certificate %s not readable" %
+ self.uefi_cert_location)
+
+ tarfile_base = os.path.basename(tarfile_path)
+ self.loader_type, self.version, self.arch = tarfile_base.split("_")
+ self.arch = self.arch.split(".")[0]
+
+ self.targetdir = os.path.join(
+ archive_root, "dists", distroseries, "main", "uefi",
+ "%s-%s" % (self.loader_type, self.arch))
+
+ def getSeriesKey(self, tarfile_path):
+ try:
+ loader_type, _, arch = os.path.basename(tarfile_path).split("_")
+ arch = arch.split(".")[0]
+ return (loader_type, arch)
+ except ValueError:
+ return None
+
+ def findEfiFilenames(self):
+ """Find all the *.efi files in an extracted tarball."""
+ for dirpath, dirnames, filenames in os.walk(self.tmpdir):
+ for filename in filenames:
+ if filename.endswith(".efi"):
+ yield os.path.join(dirpath, filename)
+
+ def getSigningCommand(self, image):
+ """Return the command used to sign an image."""
+ return [
+ "sbsign", "--key", self.uefi_key_location,
+ "--cert", self.uefi_cert_location, image,
+ ]
+
+ def sign(self, image):
+ """Sign an image."""
+ subprocess.check_call(self.getSigningCommand(image))
+
+ def extract(self):
+ """Copy the custom upload to a temporary directory, and sign it.
+
+ No actual extraction is required.
+ """
+ super(UefiUpload, self).extract()
+ efi_filenames = list(self.findEfiFilenames())
+ if not efi_filenames:
+ raise UefiNothingToSign(self.tarfile_path)
+ for efi_filename in efi_filenames:
+ remove_if_exists("%s.signed" % efi_filename)
+ self.sign(efi_filename)
+
+ def shouldInstall(self, filename):
+ return filename.startswith("%s/" % self.version)
+
+
+def process_uefi(archive_root, tarfile_path, distroseries):
+ """Process a raw-uefi tarfile.
+
+ Unpacking it into the given archive for the given distroseries.
+ Raises CustomUploadError (or some subclass thereof) if anything goes
+ wrong.
+ """
+ upload = UefiUpload()
+ upload.process(archive_root, tarfile_path, distroseries)
=== modified file 'lib/lp/archiveuploader/nascentuploadfile.py'
--- lib/lp/archiveuploader/nascentuploadfile.py 2012-05-25 15:31:50 +0000
+++ lib/lp/archiveuploader/nascentuploadfile.py 2012-06-22 15:32:21 +0000
@@ -268,6 +268,7 @@
PackageUploadCustomFormat.STATIC_TRANSLATIONS,
'raw-meta-data':
PackageUploadCustomFormat.META_DATA,
+ 'raw-uefi': PackageUploadCustomFormat.UEFI,
}
@property
@@ -294,6 +295,13 @@
restricted=self.policy.archive.private)
return libraryfile
+ def autoApprove(self):
+ """Return whether this custom upload can be automatically approved."""
+ # UEFI uploads are signed, and must therefore be approved by a human.
+ if self.custom_type == PackageUploadCustomFormat.UEFI:
+ return False
+ return True
+
class PackageUploadFile(NascentUploadFile):
"""Base class to model sources and binary files contained in a upload. """
=== modified file 'lib/lp/archiveuploader/tests/test_nascentuploadfile.py'
--- lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2012-04-27 14:20:20 +0000
+++ lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2012-06-22 15:32:21 +0000
@@ -25,6 +25,7 @@
from lp.buildmaster.enums import BuildStatus
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.log.logger import BufferLogger
+from lp.services.osutils import write_file
from lp.soyuz.enums import (
PackagePublishingStatus,
PackageUploadCustomFormat,
@@ -92,6 +93,18 @@
self.assertEquals("bla.txt", libraryfile.filename)
self.assertEquals("application/octet-stream", libraryfile.mimetype)
+ def test_debian_installer_auto_approved(self):
+ # debian-installer uploads are auto-approved.
+ uploadfile = self.createCustomUploadFile(
+ "bla.txt", "data", "main/raw-installer", "extra")
+ self.assertTrue(uploadfile.autoApprove())
+
+ def test_uefi_not_auto_approved(self):
+ # UEFI uploads are auto-approved.
+ uploadfile = self.createCustomUploadFile(
+ "bla.txt", "data", "main/raw-uefi", "extra")
+ self.assertFalse(uploadfile.autoApprove())
+
class PackageUploadFileTestCase(NascentUploadFileTestCase):
"""Base class for all tests of classes deriving from PackageUploadFile."""
@@ -286,8 +299,7 @@
"data.tar.%s" % data_format,
]
for member in members:
- with open(os.path.join(tempdir, member), "w") as f:
- pass
+ write_file(os.path.join(tempdir, member), "")
retcode = subprocess.call(
["ar", "rc", filename] + members, cwd=tempdir)
self.assertEqual(0, retcode)
=== modified file 'lib/lp/archiveuploader/uploadpolicy.py'
--- lib/lp/archiveuploader/uploadpolicy.py 2012-06-19 03:26:57 +0000
+++ lib/lp/archiveuploader/uploadpolicy.py 2012-06-22 15:32:21 +0000
@@ -317,6 +317,14 @@
raise AssertionError(
"Upload is not sourceful, binaryful or mixed.")
+ def autoApprove(self, upload):
+ """Check that all custom files in this upload can be auto-approved."""
+ if self.binaryful:
+ for custom_file in upload.changes.custom_files:
+ if not custom_file.autoApprove():
+ return False
+ return True
+
class SyncUploadPolicy(AbstractUploadPolicy):
"""This policy is invoked when processing sync uploads."""
=== modified file 'lib/lp/services/config/schema-lazr.conf'
--- lib/lp/services/config/schema-lazr.conf 2012-06-15 00:42:38 +0000
+++ lib/lp/services/config/schema-lazr.conf 2012-06-22 15:32:21 +0000
@@ -32,6 +32,18 @@
# datatype: string
run_parts_location: none
+# Location of the UEFI private key. Absolute path, or "none" to refuse
+# signing.
+#
+# datatype: string
+uefi_key_location: none
+
+# Location of the UEFI certificate. Absolute path, or "none" to refuse
+# signing.
+#
+# datatype: string
+uefi_cert_location: none
+
[binaryfile_expire]
dbuser: binaryfile-expire
=== modified file 'lib/lp/services/osutils.py'
--- lib/lp/services/osutils.py 2012-05-31 11:46:17 +0000
+++ lib/lp/services/osutils.py 2012-06-22 15:32:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Utilities for doing the sort of thing the os module does."""
@@ -15,6 +15,7 @@
'remove_tree',
'two_stage_kill',
'until_no_eintr',
+ 'write_file',
]
from contextlib import contextmanager
@@ -207,6 +208,5 @@
def write_file(path, content):
- f = open(path, 'w')
- f.write(content)
- f.close()
+ with open_for_writing(path, 'w') as f:
+ f.write(content)
=== modified file 'lib/lp/soyuz/browser/queue.py'
--- lib/lp/soyuz/browser/queue.py 2012-01-01 02:58:52 +0000
+++ lib/lp/soyuz/browser/queue.py 2012-06-22 15:32:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Browser views for package queue."""
@@ -578,6 +578,7 @@
(self.contains_installer, ("Installer", 'ubuntu-icon')),
(self.contains_upgrader, ("Upgrader", 'ubuntu-icon')),
(self.contains_ddtp, (ddtp, 'ubuntu-icon')),
+ (self.contains_uefi, ("Signed UEFI boot loader", 'ubuntu-icon')),
]
return [
self.composeIcon(*details)
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2012-06-22 05:25:35 +0000
+++ lib/lp/soyuz/configure.zcml 2012-06-22 15:32:21 +0000
@@ -173,6 +173,7 @@
contains_installer
contains_upgrader
contains_ddtp
+ contains_uefi
displayname
displayarchs
displayversion
=== modified file 'lib/lp/soyuz/enums.py'
--- lib/lp/soyuz/enums.py 2012-05-28 15:12:00 +0000
+++ lib/lp/soyuz/enums.py 2012-06-22 15:32:21 +0000
@@ -474,6 +474,12 @@
the Software Center.
""")
+ UEFI = DBItem(6, """
+ uefi
+
+ A signed UEFI boot loader image.
+ """)
+
class PackageUploadStatus(DBEnumeratedType):
"""Distro Release Queue Status
=== modified file 'lib/lp/soyuz/interfaces/queue.py'
--- lib/lp/soyuz/interfaces/queue.py 2012-05-30 08:50:50 +0000
+++ lib/lp/soyuz/interfaces/queue.py 2012-06-22 15:32:21 +0000
@@ -210,6 +210,8 @@
"wheter or not this upload contains upgrader images")
contains_ddtp = Attribute(
"wheter or not this upload contains DDTP images")
+ contains_uefi = Attribute(
+ "whether or not this upload contains a signed UEFI boot loader image")
isPPA = Attribute(
"Return True if this PackageUpload is a PPA upload.")
is_delayed_copy = Attribute(
=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py 2012-06-19 03:26:57 +0000
+++ lib/lp/soyuz/model/queue.py 2012-06-22 15:32:21 +0000
@@ -629,6 +629,12 @@
return (PackageUploadCustomFormat.DDTP_TARBALL
in self._customFormats)
+ @cachedproperty
+ def contains_uefi(self):
+ """See `IPackageUpload`."""
+ return (PackageUploadCustomFormat.UEFI
+ in self._customFormats)
+
@property
def package_name(self):
"""See `IPackageUpload`."""
@@ -1377,6 +1383,14 @@
self.libraryfilealias.open()
copy_and_close(self.libraryfilealias, file_obj)
+ def publishUefi(self, logger=None):
+ """See `IPackageUploadCustom`."""
+ # XXX cprov 2005-03-03: We need to use the Zope Component Lookup
+ # to instantiate the object in question and avoid circular imports
+ from lp.archivepublisher.uefi import process_uefi
+
+ self._publishCustom(process_uefi)
+
publisher_dispatch = {
PackageUploadCustomFormat.DEBIAN_INSTALLER: publishDebianInstaller,
PackageUploadCustomFormat.ROSETTA_TRANSLATIONS:
@@ -1386,6 +1400,7 @@
PackageUploadCustomFormat.STATIC_TRANSLATIONS:
publishStaticTranslations,
PackageUploadCustomFormat.META_DATA: publishMetaData,
+ PackageUploadCustomFormat.UEFI: publishUefi,
}
# publisher_dispatch must have an entry for each value of
=== modified file 'lib/lp/soyuz/scripts/custom_uploads_copier.py'
--- lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-05-30 10:25:43 +0000
+++ lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-06-22 15:32:21 +0000
@@ -18,6 +18,7 @@
from lp.archivepublisher.debian_installer import DebianInstallerUpload
from lp.archivepublisher.dist_upgrader import DistUpgraderUpload
+from lp.archivepublisher.uefi import UefiUpload
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.database.bulk import load_referencing
from lp.soyuz.enums import PackageUploadCustomFormat
@@ -40,6 +41,7 @@
copyable_types = {
PackageUploadCustomFormat.DEBIAN_INSTALLER: DebianInstallerUpload,
PackageUploadCustomFormat.DIST_UPGRADER: DistUpgraderUpload,
+ PackageUploadCustomFormat.UEFI: UefiUpload,
}
def __init__(self, target_series):
Follow ups