remove PREFERRED capabilities
Way back when I added support for core boot classic installs, we did not have a way to indicate whether the installed model preferred to be installed encrypted or unencrypted. Roll time on 9 months or so and our API now supports a list of capabilites and we can define that we can indicate which is preferred by putting it first in the list. One part of the implementation is a bit delicate really -- relying on list.sort() being stable -- so maybe we should clean that up. But I think the other changes make everything nicer.
This commit is contained in:
parent
3452c5772e
commit
35e66a4805
|
@ -350,13 +350,12 @@ class GuidedCapability(enum.Enum):
|
||||||
|
|
||||||
CORE_BOOT_ENCRYPTED = enum.auto()
|
CORE_BOOT_ENCRYPTED = enum.auto()
|
||||||
CORE_BOOT_UNENCRYPTED = enum.auto()
|
CORE_BOOT_UNENCRYPTED = enum.auto()
|
||||||
# These two are not valid as GuidedChoiceV2.capability:
|
|
||||||
CORE_BOOT_PREFER_ENCRYPTED = enum.auto()
|
|
||||||
CORE_BOOT_PREFER_UNENCRYPTED = enum.auto()
|
|
||||||
|
|
||||||
DD = enum.auto()
|
DD = enum.auto()
|
||||||
|
|
||||||
def __lt__(self, other) -> bool:
|
def __lt__(self, other) -> bool:
|
||||||
|
if self.is_core_boot() and other.is_core_boot():
|
||||||
|
return False
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
def is_lvm(self) -> bool:
|
def is_lvm(self) -> bool:
|
||||||
|
@ -366,8 +365,6 @@ class GuidedCapability(enum.Enum):
|
||||||
return self in [
|
return self in [
|
||||||
GuidedCapability.CORE_BOOT_ENCRYPTED,
|
GuidedCapability.CORE_BOOT_ENCRYPTED,
|
||||||
GuidedCapability.CORE_BOOT_UNENCRYPTED,
|
GuidedCapability.CORE_BOOT_UNENCRYPTED,
|
||||||
GuidedCapability.CORE_BOOT_PREFER_ENCRYPTED,
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_UNENCRYPTED,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def supports_manual_customization(self) -> bool:
|
def supports_manual_customization(self) -> bool:
|
||||||
|
|
|
@ -394,11 +394,13 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
info.capability_info.allowed = [GuidedCapability.CORE_BOOT_ENCRYPTED]
|
info.capability_info.allowed = [GuidedCapability.CORE_BOOT_ENCRYPTED]
|
||||||
elif se.storage_safety == StorageSafety.PREFER_ENCRYPTED:
|
elif se.storage_safety == StorageSafety.PREFER_ENCRYPTED:
|
||||||
info.capability_info.allowed = [
|
info.capability_info.allowed = [
|
||||||
GuidedCapability.CORE_BOOT_PREFER_ENCRYPTED
|
GuidedCapability.CORE_BOOT_ENCRYPTED,
|
||||||
|
GuidedCapability.CORE_BOOT_UNENCRYPTED,
|
||||||
]
|
]
|
||||||
elif se.storage_safety == StorageSafety.PREFER_UNENCRYPTED:
|
elif se.storage_safety == StorageSafety.PREFER_UNENCRYPTED:
|
||||||
info.capability_info.allowed = [
|
info.capability_info.allowed = [
|
||||||
GuidedCapability.CORE_BOOT_PREFER_UNENCRYPTED
|
GuidedCapability.CORE_BOOT_UNENCRYPTED,
|
||||||
|
GuidedCapability.CORE_BOOT_ENCRYPTED,
|
||||||
]
|
]
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
@ -651,26 +653,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
|
|
||||||
def set_info_for_capability(self, capability: GuidedCapability):
|
def set_info_for_capability(self, capability: GuidedCapability):
|
||||||
"""Given a request for a capability, select the variation to use."""
|
"""Given a request for a capability, select the variation to use."""
|
||||||
if capability == GuidedCapability.CORE_BOOT_ENCRYPTED:
|
|
||||||
# If the request is for encryption, a variation with any
|
|
||||||
# of these capabilities is OK:
|
|
||||||
caps = {
|
|
||||||
GuidedCapability.CORE_BOOT_ENCRYPTED,
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_ENCRYPTED,
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_UNENCRYPTED,
|
|
||||||
}
|
|
||||||
elif capability == GuidedCapability.CORE_BOOT_UNENCRYPTED:
|
|
||||||
# Similar if the request is for uncrypted
|
|
||||||
caps = {
|
|
||||||
GuidedCapability.CORE_BOOT_UNENCRYPTED,
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_ENCRYPTED,
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_UNENCRYPTED,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Otherwise, just look for what we were asked for.
|
|
||||||
caps = {capability}
|
|
||||||
for info in self._variation_info.values():
|
for info in self._variation_info.values():
|
||||||
if caps & set(info.capability_info.allowed):
|
if capability in info.capability_info.allowed:
|
||||||
self._info = info
|
self._info = info
|
||||||
return
|
return
|
||||||
raise Exception("could not find variation for {}".format(capability))
|
raise Exception("could not find variation for {}".format(capability))
|
||||||
|
@ -1313,12 +1297,12 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
if name == "hybrid":
|
if name == "hybrid":
|
||||||
# this check is conceptually unnecessary but results in a
|
# this check is conceptually unnecessary but results in a
|
||||||
# much cleaner error message...
|
# much cleaner error message...
|
||||||
core_boot_caps = set()
|
core_boot_caps = []
|
||||||
for variation in self._variation_info.values():
|
for variation in self._variation_info.values():
|
||||||
if not variation.is_valid():
|
if not variation.is_valid():
|
||||||
continue
|
continue
|
||||||
if variation.is_core_boot_classic():
|
if variation.is_core_boot_classic():
|
||||||
core_boot_caps.update(variation.capability_info.allowed)
|
core_boot_caps.extend(variation.capability_info.allowed)
|
||||||
if not core_boot_caps:
|
if not core_boot_caps:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"can only use name: hybrid when installing core boot classic"
|
"can only use name: hybrid when installing core boot classic"
|
||||||
|
@ -1328,18 +1312,12 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
encrypted = layout.get("encrypted", None)
|
encrypted = layout.get("encrypted", None)
|
||||||
GC = GuidedCapability
|
GC = GuidedCapability
|
||||||
if encrypted is None:
|
if encrypted is None:
|
||||||
if (
|
capability = core_boot_caps[0]
|
||||||
GC.CORE_BOOT_ENCRYPTED in core_boot_caps
|
|
||||||
or GC.CORE_BOOT_PREFER_ENCRYPTED in core_boot_caps
|
|
||||||
):
|
|
||||||
capability = GC.CORE_BOOT_ENCRYPTED
|
|
||||||
else:
|
|
||||||
capability = GC.CORE_BOOT_UNENCRYPTED
|
|
||||||
elif encrypted:
|
elif encrypted:
|
||||||
capability = GC.CORE_BOOT_ENCRYPTED
|
capability = GC.CORE_BOOT_ENCRYPTED
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
core_boot_caps == {GuidedCapability.CORE_BOOT_ENCRYPTED}
|
core_boot_caps == [GuidedCapability.CORE_BOOT_ENCRYPTED]
|
||||||
and not encrypted
|
and not encrypted
|
||||||
):
|
):
|
||||||
raise Exception("cannot install this model unencrypted")
|
raise Exception("cannot install this model unencrypted")
|
||||||
|
|
|
@ -696,7 +696,9 @@ class TestLayout(IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestGuidedV2(IsolatedAsyncioTestCase):
|
class TestGuidedV2(IsolatedAsyncioTestCase):
|
||||||
async def _setup(self, bootloader, ptable, fix_bios=True, **kw):
|
async def _setup(
|
||||||
|
self, bootloader, ptable, fix_bios=True, snapd_system_labels=(None,), **kw
|
||||||
|
):
|
||||||
self.app = make_app()
|
self.app = make_app()
|
||||||
self.app.opts.bootloader = bootloader.value
|
self.app.opts.bootloader = bootloader.value
|
||||||
self.fsc = FilesystemController(app=self.app)
|
self.fsc = FilesystemController(app=self.app)
|
||||||
|
@ -704,11 +706,16 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
|
||||||
self.fsc.calculate_suggested_install_min.return_value = 10 << 30
|
self.fsc.calculate_suggested_install_min.return_value = 10 << 30
|
||||||
self.fsc.model = self.model = make_model(bootloader)
|
self.fsc.model = self.model = make_model(bootloader)
|
||||||
self.fsc._examine_systems_task.start_sync()
|
self.fsc._examine_systems_task.start_sync()
|
||||||
|
self.app.snapdapi = snapdapi.make_api_client(AsyncSnapd(get_fake_connection()))
|
||||||
self.app.dr_cfg = DRConfig()
|
self.app.dr_cfg = DRConfig()
|
||||||
|
self.app.dr_cfg.systems_dir_exists = True
|
||||||
|
self.app.base_model.source.search_drivers = False
|
||||||
self.app.base_model.source.current.type = "fsimage"
|
self.app.base_model.source.current.type = "fsimage"
|
||||||
self.app.base_model.source.current.variations = {
|
variations = self.app.base_model.source.current.variations = {}
|
||||||
"default": CatalogEntryVariation(path="", size=1),
|
for label in snapd_system_labels:
|
||||||
}
|
variations["var" + str(label)] = CatalogEntryVariation(
|
||||||
|
path="", size=1, snapd_system_label=label
|
||||||
|
)
|
||||||
self.app.controllers.Source.get_handler.return_value = TrivialSourceHandler("")
|
self.app.controllers.Source.get_handler.return_value = TrivialSourceHandler("")
|
||||||
await self.fsc._examine_systems_task.wait()
|
await self.fsc._examine_systems_task.wait()
|
||||||
self.disk = make_disk(self.model, ptable=ptable, **kw)
|
self.disk = make_disk(self.model, ptable=ptable, **kw)
|
||||||
|
@ -719,7 +726,7 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
|
||||||
"filesystem": self.fs_probe,
|
"filesystem": self.fs_probe,
|
||||||
}
|
}
|
||||||
self.fsc._probe_task.task = mock.Mock()
|
self.fsc._probe_task.task = mock.Mock()
|
||||||
self.fsc._examine_systems_task.task = mock.Mock()
|
# self.fsc._examine_systems_task.task = mock.Mock()
|
||||||
if bootloader == Bootloader.BIOS and ptable != "msdos" and fix_bios:
|
if bootloader == Bootloader.BIOS and ptable != "msdos" and fix_bios:
|
||||||
make_partition(
|
make_partition(
|
||||||
self.model,
|
self.model,
|
||||||
|
@ -1153,6 +1160,29 @@ class TestGuidedV2(IsolatedAsyncioTestCase):
|
||||||
self.assertEqual(expected, resp.targets)
|
self.assertEqual(expected, resp.targets)
|
||||||
self.assertEqual(ProbeStatus.DONE, resp.status)
|
self.assertEqual(ProbeStatus.DONE, resp.status)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
("mandatory", ["ENCRYPTED"]),
|
||||||
|
("prefer-encrypted", ["ENCRYPTED", "UNENCRYPTED"]),
|
||||||
|
("prefer-unencrypted", ["UNENCRYPTED", "ENCRYPTED"]),
|
||||||
|
("unavailable", ["UNENCRYPTED"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
async def test_core_boot_and_classic(self, label, core_boot_cap_names):
|
||||||
|
expected_core_boot_caps = [
|
||||||
|
getattr(GuidedCapability, f"CORE_BOOT_{cap_name}")
|
||||||
|
for cap_name in core_boot_cap_names
|
||||||
|
]
|
||||||
|
await self._setup(
|
||||||
|
Bootloader.UEFI,
|
||||||
|
"gpt",
|
||||||
|
snapd_system_labels=[None, label],
|
||||||
|
)
|
||||||
|
resp = await self.fsc.v2_guided_GET()
|
||||||
|
[target, man] = resp.targets
|
||||||
|
got_core_boot_caps = [cap for cap in target.allowed if cap.is_core_boot()]
|
||||||
|
self.assertEqual(got_core_boot_caps, expected_core_boot_caps)
|
||||||
|
|
||||||
|
|
||||||
class TestManualBoot(IsolatedAsyncioTestCase):
|
class TestManualBoot(IsolatedAsyncioTestCase):
|
||||||
def _setup(self, bootloader, ptable, **kw):
|
def _setup(self, bootloader, ptable, **kw):
|
||||||
|
|
|
@ -629,7 +629,7 @@ class TestCore(TestAPI):
|
||||||
await inst.post("/source", source_id="ubuntu-desktop")
|
await inst.post("/source", source_id="ubuntu-desktop")
|
||||||
resp = await inst.get("/storage/v2/guided", wait=True)
|
resp = await inst.get("/storage/v2/guided", wait=True)
|
||||||
[reformat, manual] = resp["targets"]
|
[reformat, manual] = resp["targets"]
|
||||||
self.assertIn("CORE_BOOT_PREFER_ENCRYPTED", reformat["allowed"])
|
self.assertIn("CORE_BOOT_ENCRYPTED", reformat["allowed"])
|
||||||
data = dict(target=reformat, capability="CORE_BOOT_ENCRYPTED")
|
data = dict(target=reformat, capability="CORE_BOOT_ENCRYPTED")
|
||||||
await inst.post("/storage/v2/guided", data)
|
await inst.post("/storage/v2/guided", data)
|
||||||
v2resp = await inst.get("/storage/v2")
|
v2resp = await inst.get("/storage/v2")
|
||||||
|
|
|
@ -134,16 +134,11 @@ class TPMChoice:
|
||||||
help: str
|
help: str
|
||||||
|
|
||||||
|
|
||||||
tpm_help_texts = {
|
tpm_help_texts_1 = {
|
||||||
"AVAILABLE_CAN_BE_DESELECTED": _(
|
GuidedCapability.CORE_BOOT_ENCRYPTED: _(
|
||||||
"The entire disk will be encrypted and protected by the "
|
|
||||||
"TPM. If this option is deselected, the disk will be "
|
|
||||||
"unencrypted and without any protection."
|
|
||||||
),
|
|
||||||
"AVAILABLE_CANNOT_BE_DESELECTED": _(
|
|
||||||
"The entire disk will be encrypted and protected by the TPM."
|
"The entire disk will be encrypted and protected by the TPM."
|
||||||
),
|
),
|
||||||
"UNAVAILABLE":
|
GuidedCapability.CORE_BOOT_UNENCRYPTED:
|
||||||
# for translators: 'reason' is the reason FDE is unavailable.
|
# for translators: 'reason' is the reason FDE is unavailable.
|
||||||
_(
|
_(
|
||||||
"TPM backed full-disk encryption is not available "
|
"TPM backed full-disk encryption is not available "
|
||||||
|
@ -151,22 +146,11 @@ tpm_help_texts = {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
choices = {
|
tpm_help_texts_can_be_deselected = _(
|
||||||
GuidedCapability.CORE_BOOT_ENCRYPTED: TPMChoice(
|
"The entire disk will be encrypted and protected by the "
|
||||||
enabled=False,
|
"TPM. If this option is deselected, the disk will be "
|
||||||
default=True,
|
"unencrypted and without any protection."
|
||||||
help=tpm_help_texts["AVAILABLE_CANNOT_BE_DESELECTED"],
|
)
|
||||||
),
|
|
||||||
GuidedCapability.CORE_BOOT_UNENCRYPTED: TPMChoice(
|
|
||||||
enabled=False, default=False, help=tpm_help_texts["UNAVAILABLE"]
|
|
||||||
),
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_ENCRYPTED: TPMChoice(
|
|
||||||
enabled=True, default=True, help=tpm_help_texts["AVAILABLE_CAN_BE_DESELECTED"]
|
|
||||||
),
|
|
||||||
GuidedCapability.CORE_BOOT_PREFER_UNENCRYPTED: TPMChoice(
|
|
||||||
enabled=True, default=False, help=tpm_help_texts["AVAILABLE_CAN_BE_DESELECTED"]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GuidedChoiceForm(SubForm):
|
class GuidedChoiceForm(SubForm):
|
||||||
|
@ -223,16 +207,28 @@ class GuidedChoiceForm(SubForm):
|
||||||
self.use_lvm.enabled = GuidedCapability.LVM in val.allowed
|
self.use_lvm.enabled = GuidedCapability.LVM in val.allowed
|
||||||
core_boot_caps = [c for c in val.allowed if c.is_core_boot()]
|
core_boot_caps = [c for c in val.allowed if c.is_core_boot()]
|
||||||
if core_boot_caps:
|
if core_boot_caps:
|
||||||
assert len(val.allowed) == 1
|
assert len(val.allowed) == len(core_boot_caps)
|
||||||
cap = core_boot_caps[0]
|
|
||||||
reason = ""
|
reason = ""
|
||||||
for disallowed in val.disallowed:
|
for disallowed in val.disallowed:
|
||||||
if disallowed.capability == GuidedCapability.CORE_BOOT_ENCRYPTED:
|
if disallowed.capability == GuidedCapability.CORE_BOOT_ENCRYPTED:
|
||||||
reason = disallowed.message
|
reason = disallowed.message
|
||||||
self.tpm_choice = choices[cap]
|
|
||||||
|
cap0 = core_boot_caps[0]
|
||||||
|
if len(core_boot_caps) == 1:
|
||||||
|
self.tpm_choice = TPMChoice(
|
||||||
|
enabled=False,
|
||||||
|
default=cap0 == GuidedCapability.CORE_BOOT_ENCRYPTED,
|
||||||
|
help=tpm_help_texts_1[cap0],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.tpm_choice = TPMChoice(
|
||||||
|
enabled=True,
|
||||||
|
default=cap0 == GuidedCapability.CORE_BOOT_ENCRYPTED,
|
||||||
|
help=tpm_help_texts_can_be_deselected,
|
||||||
|
)
|
||||||
self.use_tpm.enabled = self.tpm_choice.enabled
|
self.use_tpm.enabled = self.tpm_choice.enabled
|
||||||
self.use_tpm.value = self.tpm_choice.default
|
self.use_tpm.value = self.tpm_choice.default
|
||||||
self.use_tpm.help = self.tpm_choice.help
|
|
||||||
self.use_tpm.help = self.tpm_choice.help.format(reason=reason)
|
self.use_tpm.help = self.tpm_choice.help.format(reason=reason)
|
||||||
else:
|
else:
|
||||||
self.use_tpm.enabled = False
|
self.use_tpm.enabled = False
|
||||||
|
|
Loading…
Reference in New Issue