Merge pull request #1993 from mwhudson/allow-system-seed-in-live-layer
allow system definition to be in live layer
This commit is contained in:
commit
eb3c4bf2ee
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"type": "sync",
|
||||||
|
"status-code": 200,
|
||||||
|
"status": "OK",
|
||||||
|
"result": {
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"current": true,
|
||||||
|
"label": "20240314",
|
||||||
|
"model": {
|
||||||
|
"model": "ubuntu-core-24-amd64-dangerous",
|
||||||
|
"brand-id": "canonical",
|
||||||
|
"display-name": "ubuntu-core-24-amd64-dangerous"
|
||||||
|
},
|
||||||
|
"brand": {
|
||||||
|
"id": "canonical",
|
||||||
|
"username": "canonical",
|
||||||
|
"display-name": "Canonical",
|
||||||
|
"validation": "verified"
|
||||||
|
},
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"title": "Reinstall",
|
||||||
|
"mode": "install"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Recover",
|
||||||
|
"mode": "recover"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Factory reset",
|
||||||
|
"mode": "factory-reset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Run normally",
|
||||||
|
"mode": "run"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default-recovery-system": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import pyudev
|
import pyudev
|
||||||
|
@ -184,6 +184,7 @@ class VariationInfo:
|
||||||
capability_info: CapabilityInfo = attr.Factory(CapabilityInfo)
|
capability_info: CapabilityInfo = attr.Factory(CapabilityInfo)
|
||||||
min_size: Optional[int] = None
|
min_size: Optional[int] = None
|
||||||
system: Optional[SystemDetails] = None
|
system: Optional[SystemDetails] = None
|
||||||
|
needs_systems_mount: bool = False
|
||||||
|
|
||||||
def is_core_boot_classic(self) -> bool:
|
def is_core_boot_classic(self) -> bool:
|
||||||
return self.label is not None
|
return self.label is not None
|
||||||
|
@ -346,20 +347,36 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
self._source_handler.cleanup()
|
self._source_handler.cleanup()
|
||||||
self._source_handler = None
|
self._source_handler = None
|
||||||
|
|
||||||
async def _get_system(self, variation_name, label):
|
async def _get_system(
|
||||||
|
self, variation_name, label
|
||||||
|
) -> Tuple[Optional[SystemDetails], bool]:
|
||||||
|
"""Return system information for a given system label.
|
||||||
|
|
||||||
|
The return value is a SystemDetails object (if any) and True if
|
||||||
|
the system was found in the layer that the installer is running
|
||||||
|
in or False if the source layer needed to be mounted to find
|
||||||
|
it.
|
||||||
|
"""
|
||||||
|
systems = await self.app.snapdapi.v2.systems.GET()
|
||||||
|
labels = {system.label for system in systems.systems}
|
||||||
|
if label in labels:
|
||||||
|
try:
|
||||||
|
return await self.app.snapdapi.v2.systems[label].GET(), True
|
||||||
|
except requests.exceptions.HTTPError as http_err:
|
||||||
|
log.warning("v2/systems/%s returned %s", label, http_err.response.text)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
await self._mount_systems_dir(variation_name)
|
await self._mount_systems_dir(variation_name)
|
||||||
except NoSnapdSystemsOnSource:
|
except NoSnapdSystemsOnSource:
|
||||||
return None
|
return None, False
|
||||||
try:
|
try:
|
||||||
system = await self.app.snapdapi.v2.systems[label].GET()
|
return await self.app.snapdapi.v2.systems[label].GET(), False
|
||||||
except requests.exceptions.HTTPError as http_err:
|
except requests.exceptions.HTTPError as http_err:
|
||||||
log.warning("v2/systems/%s returned %s", label, http_err.response.text)
|
log.warning("v2/systems/%s returned %s", label, http_err.response.text)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
await self._unmount_systems_dir()
|
await self._unmount_systems_dir()
|
||||||
log.debug("got system %s", system)
|
|
||||||
return system
|
|
||||||
|
|
||||||
def info_for_system(self, name: str, label: str, system: SystemDetails):
|
def info_for_system(self, name: str, label: str, system: SystemDetails):
|
||||||
if len(system.volumes) > 1:
|
if len(system.volumes) > 1:
|
||||||
|
@ -420,40 +437,16 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
async def _examine_systems(self):
|
def _maybe_disable_encryption(self, info: VariationInfo) -> None:
|
||||||
self._variation_info.clear()
|
|
||||||
catalog_entry = self.app.base_model.source.current
|
|
||||||
for name, variation in catalog_entry.variations.items():
|
|
||||||
system = None
|
|
||||||
label = variation.snapd_system_label
|
|
||||||
if label is not None:
|
|
||||||
system = await self._get_system(name, label)
|
|
||||||
log.debug("got system %s for variation %s", system, name)
|
|
||||||
if system is not None and len(system.volumes) > 0:
|
|
||||||
if not self.app.opts.enhanced_secureboot:
|
|
||||||
log.debug("Not offering enhanced_secureboot: commandline disabled")
|
|
||||||
continue
|
|
||||||
info = self.info_for_system(name, label, system)
|
|
||||||
if info is None:
|
|
||||||
continue
|
|
||||||
if self.model.bootloader != Bootloader.UEFI:
|
if self.model.bootloader != Bootloader.UEFI:
|
||||||
log.debug(
|
log.debug("Disabling core boot based install options on non-UEFI system")
|
||||||
"Disabling core boot based install options on non-UEFI "
|
|
||||||
"system"
|
|
||||||
)
|
|
||||||
info.capability_info.disallow_if(
|
info.capability_info.disallow_if(
|
||||||
lambda cap: cap.is_core_boot(),
|
lambda cap: cap.is_core_boot(),
|
||||||
GuidedDisallowedCapabilityReason.NOT_UEFI,
|
GuidedDisallowedCapabilityReason.NOT_UEFI,
|
||||||
_(
|
_("Enhanced secure boot options only available on UEFI systems."),
|
||||||
"Enhanced secure boot options only available on UEFI "
|
|
||||||
"systems."
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
search_drivers = self.app.base_model.source.search_drivers
|
search_drivers = self.app.base_model.source.search_drivers
|
||||||
if (
|
if search_drivers is not SEARCH_DRIVERS_AUTOINSTALL_DEFAULT and search_drivers:
|
||||||
search_drivers is not SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
|
|
||||||
and search_drivers
|
|
||||||
):
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Disabling core boot based install options as third-party "
|
"Disabling core boot based install options as third-party "
|
||||||
"drivers selected"
|
"drivers selected"
|
||||||
|
@ -466,16 +459,32 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
"third party drivers."
|
"third party drivers."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self._variation_info[name] = info
|
|
||||||
|
async def _examine_systems(self):
|
||||||
|
self._variation_info.clear()
|
||||||
|
catalog_entry = self.app.base_model.source.current
|
||||||
|
for name, variation in catalog_entry.variations.items():
|
||||||
|
system = None
|
||||||
|
label = variation.snapd_system_label
|
||||||
|
if label is not None:
|
||||||
|
system, in_live_layer = await self._get_system(name, label)
|
||||||
|
log.debug("got system %s for variation %s", system, name)
|
||||||
|
if system is not None and len(system.volumes) > 0:
|
||||||
|
if not self.app.opts.enhanced_secureboot:
|
||||||
|
log.debug("Not offering enhanced_secureboot: commandline disabled")
|
||||||
|
continue
|
||||||
|
info = self.info_for_system(name, label, system)
|
||||||
|
if info is None:
|
||||||
|
continue
|
||||||
|
if not in_live_layer:
|
||||||
|
info.needs_systems_mount = True
|
||||||
|
self._maybe_disable_encryption(info)
|
||||||
elif catalog_entry.type.startswith("dd-"):
|
elif catalog_entry.type.startswith("dd-"):
|
||||||
min_size = variation.size
|
min_size = variation.size
|
||||||
self._variation_info[name] = VariationInfo.dd(
|
info = VariationInfo.dd(name=name, min_size=min_size)
|
||||||
name=name, min_size=min_size
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._variation_info[name] = VariationInfo.classic(
|
info = VariationInfo.classic(name=name, min_size=variation.size)
|
||||||
name=name, min_size=variation.size
|
self._variation_info[name] = info
|
||||||
)
|
|
||||||
|
|
||||||
@with_context()
|
@with_context()
|
||||||
async def apply_autoinstall_config(self, context=None):
|
async def apply_autoinstall_config(self, context=None):
|
||||||
|
@ -866,9 +875,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
|
||||||
offset = offset + structure.size
|
offset = offset + structure.size
|
||||||
|
|
||||||
async def guided_core_boot(self, disk: Disk):
|
async def guided_core_boot(self, disk: Disk):
|
||||||
|
if self._info.needs_systems_mount:
|
||||||
|
await self._mount_systems_dir(self._info.name)
|
||||||
# Formatting for a core boot classic system relies on some curtin
|
# Formatting for a core boot classic system relies on some curtin
|
||||||
# features that are only available with v2 partitioning.
|
# features that are only available with v2 partitioning.
|
||||||
await self._mount_systems_dir(self._info.name)
|
|
||||||
self.model.storage_version = 2
|
self.model.storage_version = 2
|
||||||
[volume] = self._info.system.volumes.values()
|
[volume] = self._info.system.volumes.values()
|
||||||
self._on_volume = snapdapi.OnVolume.from_volume(volume)
|
self._on_volume = snapdapi.OnVolume.from_volume(volume)
|
||||||
|
|
|
@ -432,6 +432,18 @@ class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
requests_mocker = requests_mock.Mocker()
|
requests_mocker = requests_mock.Mocker()
|
||||||
|
requests_mocker.get(
|
||||||
|
"http+unix://snapd/v2/systems",
|
||||||
|
json={
|
||||||
|
"type": "sync",
|
||||||
|
"status-code": 200,
|
||||||
|
"status": "OK",
|
||||||
|
"result": {
|
||||||
|
"systems": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
"http+unix://snapd/v2/systems/enhanced-secureboot-desktop",
|
"http+unix://snapd/v2/systems/enhanced-secureboot-desktop",
|
||||||
json=json_body,
|
json=json_body,
|
||||||
|
@ -1397,9 +1409,10 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
|
||||||
name="foo",
|
name="foo",
|
||||||
label="system",
|
label="system",
|
||||||
system=snapdapi.SystemDetails(
|
system=snapdapi.SystemDetails(
|
||||||
|
label="system",
|
||||||
volumes={
|
volumes={
|
||||||
"pc": snapdapi.Volume(schema="gpt", structure=structures),
|
"pc": snapdapi.Volume(schema="gpt", structure=structures),
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,7 @@ class StorageEncryption:
|
||||||
|
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class SystemDetails:
|
class SystemDetails:
|
||||||
|
label: str
|
||||||
current: bool = False
|
current: bool = False
|
||||||
volumes: Dict[str, Volume] = attr.Factory(dict)
|
volumes: Dict[str, Volume] = attr.Factory(dict)
|
||||||
storage_encryption: Optional[StorageEncryption] = named_field(
|
storage_encryption: Optional[StorageEncryption] = named_field(
|
||||||
|
@ -210,6 +211,11 @@ class SystemDetails:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class SystemsResponse:
|
||||||
|
systems: List[SystemDetails] = attr.Factory(list)
|
||||||
|
|
||||||
|
|
||||||
class SystemAction(enum.Enum):
|
class SystemAction(enum.Enum):
|
||||||
INSTALL = "install"
|
INSTALL = "install"
|
||||||
|
|
||||||
|
@ -258,6 +264,9 @@ class SnapdAPI:
|
||||||
...
|
...
|
||||||
|
|
||||||
class systems:
|
class systems:
|
||||||
|
def GET() -> SystemsResponse:
|
||||||
|
...
|
||||||
|
|
||||||
@path_parameter
|
@path_parameter
|
||||||
class label:
|
class label:
|
||||||
def GET() -> SystemDetails:
|
def GET() -> SystemDetails:
|
||||||
|
|
Loading…
Reference in New Issue