diff --git a/subiquity/server/controllers/identity.py b/subiquity/server/controllers/identity.py index 66e242f2..47981a75 100644 --- a/subiquity/server/controllers/identity.py +++ b/subiquity/server/controllers/identity.py @@ -22,8 +22,8 @@ import attr from subiquity.common.apidef import API from subiquity.common.resources import resource_path from subiquity.common.types import IdentityData, UsernameValidation +from subiquity.server.autoinstall import AutoinstallError from subiquity.server.controller import SubiquityController -from subiquitycore.context import with_context log = logging.getLogger("subiquity.server.controllers.identity") @@ -79,18 +79,29 @@ class IdentityController(SubiquityController): crypted_password=data["password"], ) self.model.add_user(identity_data) - - @with_context() - async def apply_autoinstall_config(self, context=None): - if self.model.user: return + + # The identity section is required except if (any): + # 1. a user-data section is provided if "user-data" in self.app.autoinstall_config: return - if self.app.base_model.target is None: - return + # 2. we are installing not-Server (Desktop) if self.app.base_model.source.current.variant != "server": return - raise Exception("neither identity nor user-data provided") + # 3. we are only refreshing the reset partition + # (The identity controller doesn't figure this out until the apply + # step, so we are going to cheat and inspect the situation here) + storage_config = self.app.autoinstall_config.get("storage") + if ( + storage_config is not None + and storage_config.get("layout") is not None + and storage_config["layout"].get("reset-partition-only") + ): + return + # 4. identity section is interactive + if self.interactive(): + return + raise AutoinstallError("neither identity nor user-data provided") def make_autoinstall(self): if self.model.user is None: diff --git a/subiquity/server/controllers/tests/test_identity.py b/subiquity/server/controllers/tests/test_identity.py index 5f2118a0..c05e4a9d 100644 --- a/subiquity/server/controllers/tests/test_identity.py +++ b/subiquity/server/controllers/tests/test_identity.py @@ -16,6 +16,7 @@ import jsonschema from jsonschema.validators import validator_for +from subiquity.server.autoinstall import AutoinstallError from subiquity.server.controllers.identity import IdentityController from subiquitycore.tests import SubiTestCase from subiquitycore.tests.mocks import make_app @@ -42,10 +43,48 @@ class TestControllerUserCreationFlows(SubiTestCase): async def test_server_requires_identity_case_4a1(self): self.app.base_model.source.current.variant = "server" - with self.assertRaises(Exception): - await self.ic.apply_autoinstall_config() + + # Autoinstall: no identity or user data and identity is not interactive + self.app.autoinstall_config = {"interactive-sections": ["not-identity"]} + with self.assertRaises(AutoinstallError): + self.ic.load_autoinstall_data(None) + + async def test_server_requires_identity_case_4a1__ok_interactive(self): + """Test no require identity for interactive identity""" + self.app.base_model.source.current.variant = "server" + + # Explicitly interactive + self.app.autoinstall_config = {"interactive-sections": ["identity"]} + self.ic.load_autoinstall_data(None) + + # Implicitly interactive + self.app.autoinstall_config = {"interactive-sections": ["*"]} + self.ic.load_autoinstall_data(None) + + # No Autoinstall => interactive + self.app.autoinstall_config = {} + self.ic.load_autoinstall_data(None) + + async def test_server_requires_identity_case_4a1__reset_only_true(self): + """Test no require identity for reset-partition-only=yes.""" + self.app.base_model.source.current.variant = "server" + + # No raise if reset-parition-only specified + self.app.autoinstall_config = { + "storage": {"layout": {"reset-partition-only": True}} + } + self.ic.load_autoinstall_data(None) + + async def test_server_requires_identity_case_4a1__reset_only_false(self): + """Test require identity for reset-partition-only=no.""" + self.app.base_model.source.current.variant = "server" + + # raises if no reset-parition-only in storage:layout: + self.app.autoinstall_config = {"storage": {"layout": {}}} + with self.assertRaises(AutoinstallError): + self.ic.load_autoinstall_data(None) async def test_desktop_does_not_require_identity_case_4a2(self): self.app.base_model.source.current.variant = "desktop" - await self.ic.apply_autoinstall_config() + self.ic.load_autoinstall_data(None) # should not raise