From 0c73bf9b86edf36cc6f9126d141a77100fbea1ea Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Wed, 3 Aug 2022 11:54:06 +0200 Subject: [PATCH 1/3] source: add ability to select source in autoinstall The source autoinstall section now supports the "id" field where the user can supply the ID of a source, e.g., "ubuntu-server" or "ubuntu-server-minimal". If the field is not supplied, the installation will use the source declared default: true (if any) in the source catalog. Otherwise, it the first source declared will be used. Signed-off-by: Olivier Gayot --- autoinstall-schema.json | 3 +++ examples/autoinstall-user-data.yaml | 2 ++ scripts/runtests.sh | 5 ++++- subiquity/models/source.py | 7 +++++++ subiquity/server/controllers/source.py | 26 +++++++++++++++++++++----- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/autoinstall-schema.json b/autoinstall-schema.json index a6bf8e63..34a5c22c 100644 --- a/autoinstall-schema.json +++ b/autoinstall-schema.json @@ -123,6 +123,9 @@ "properties": { "search_drivers": { "type": "boolean" + }, + "id": { + "type": "string" } }, "required": [ diff --git a/examples/autoinstall-user-data.yaml b/examples/autoinstall-user-data.yaml index e7de1003..dfa8b535 100644 --- a/examples/autoinstall-user-data.yaml +++ b/examples/autoinstall-user-data.yaml @@ -13,6 +13,8 @@ late-commands: - echo a keyboard: layout: gb +source: + id: ubuntu-server-minimal updates: security user-data: users: diff --git a/scripts/runtests.sh b/scripts/runtests.sh index 1e9e4faf..88686abb 100755 --- a/scripts/runtests.sh +++ b/scripts/runtests.sh @@ -225,8 +225,11 @@ LANG=C.UTF-8 timeout --foreground 60 \ --output-base "$tmpdir" \ --machine-config examples/simple.json \ --autoinstall examples/autoinstall-user-data.yaml \ - --kernel-cmdline autoinstall + --kernel-cmdline autoinstall \ + --source-catalog examples/install-sources.yaml validate +python3 scripts/check-yaml-fields.py "$tmpdir"/var/log/installer/autoinstall-user-data \ + 'autoinstall.source.id="ubuntu-server-minimal"' grep -q 'finish: subiquity/Install/install/postinstall/run_unattended_upgrades: SUCCESS: downloading and installing security updates' $tmpdir/subiquity-server-debug.log # The OOBE doesn't exist in WSL < 20.04 diff --git a/subiquity/models/source.py b/subiquity/models/source.py index e1df107c..d326f9ce 100644 --- a/subiquity/models/source.py +++ b/subiquity/models/source.py @@ -88,6 +88,13 @@ class SourceModel: if self.current is None: self.current = self.sources[0] + def get_matching_source(self, id_: str) -> CatalogEntry: + """ Return a source object that has the ID requested. """ + for source in self.sources: + if source.id == id_: + return source + raise KeyError + def get_source(self): path = os.path.join(self._dir, self.current.path) if self.current.preinstalled_langs: diff --git a/subiquity/server/controllers/source.py b/subiquity/server/controllers/source.py index 031be32e..06373a9c 100644 --- a/subiquity/server/controllers/source.py +++ b/subiquity/server/controllers/source.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import contextlib from typing import Any, Optional import os @@ -59,6 +60,9 @@ class SourceController(SubiquityController): "search_drivers": { "type": "boolean", }, + "id": { + "type": "string", + }, }, "required": ["search_drivers"], } @@ -71,15 +75,19 @@ class SourceController(SubiquityController): super().__init__(app) self._handler = None self.source_path: Optional[str] = None + self.ai_source_id: Optional[str] = None def make_autoinstall(self): - return {"search_drivers": self.model.search_drivers} + return { + "search_drivers": self.model.search_drivers, + "id": self.model.current.id, + } def load_autoinstall_data(self, data: Any) -> None: if data is None: # For some reason, the schema validator does not reject # "source: null" despite "type" being "object" - data = self.autoinstall_default + data = {**self.autoinstall_default, "id": None} # search_drivers is marked required so the schema validator should # reject any missing data. @@ -87,6 +95,15 @@ class SourceController(SubiquityController): self.model.search_drivers = data["search_drivers"] + # At this point, the model has not yet loaded the sources from the + # catalog. So we store the data and lean on apply_autoinstall_config. + self.ai_source_id = data.get("id") + + async def apply_autoinstall_config(self) -> None: + if self.ai_source_id is None: + return + self.model.current = self.model.get_matching_source(self.ai_source_id) + def start(self): path = '/cdrom/casper/install-sources.yaml' if self.app.opts.source_catalog is not None: @@ -129,7 +146,6 @@ class SourceController(SubiquityController): async def POST(self, source_id: str, search_drivers: bool = False) -> None: self.model.search_drivers = search_drivers - for source in self.model.sources: - if source.id == source_id: - self.model.current = source + with contextlib.suppress(KeyError): + self.model.current = self.model.get_matching_source(source_id) await self.configured() From 566f32b01efbcbd13b0bcf643fb5fe6b3c42f959 Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Wed, 3 Aug 2022 13:56:08 +0200 Subject: [PATCH 2/3] source: don't mark search_drivers required In the source autoinstall section, the search_drivers key was marked required. This made sense at the time when it was the only supported key. However, now that we also support the source ID, we don't want to force the user to supply search_drivers as well. Signed-off-by: Olivier Gayot --- autoinstall-schema.json | 5 +---- subiquity/server/controllers/source.py | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/autoinstall-schema.json b/autoinstall-schema.json index 34a5c22c..05b21120 100644 --- a/autoinstall-schema.json +++ b/autoinstall-schema.json @@ -127,10 +127,7 @@ "id": { "type": "string" } - }, - "required": [ - "search_drivers" - ] + } }, "network": { "oneOf": [ diff --git a/subiquity/server/controllers/source.py b/subiquity/server/controllers/source.py index 06373a9c..9c04d5df 100644 --- a/subiquity/server/controllers/source.py +++ b/subiquity/server/controllers/source.py @@ -64,7 +64,6 @@ class SourceController(SubiquityController): "type": "string", }, }, - "required": ["search_drivers"], } # Defaults to true for backward compatibility with existing autoinstall # configurations. Back then, then users were able to install third-party @@ -89,11 +88,7 @@ class SourceController(SubiquityController): # "source: null" despite "type" being "object" data = {**self.autoinstall_default, "id": None} - # search_drivers is marked required so the schema validator should - # reject any missing data. - assert "search_drivers" in data - - self.model.search_drivers = data["search_drivers"] + self.model.search_drivers = data.get("search_drivers", True) # At this point, the model has not yet loaded the sources from the # catalog. So we store the data and lean on apply_autoinstall_config. From 3ca78bf07b7a189d1b1f2e4f68bcd18e42c15002 Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Wed, 3 Aug 2022 20:11:09 +0200 Subject: [PATCH 3/3] source: explain why the autoinstall data can be null/None Signed-off-by: Olivier Gayot --- subiquity/server/controllers/source.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/subiquity/server/controllers/source.py b/subiquity/server/controllers/source.py index 9c04d5df..e6180f90 100644 --- a/subiquity/server/controllers/source.py +++ b/subiquity/server/controllers/source.py @@ -84,8 +84,10 @@ class SourceController(SubiquityController): def load_autoinstall_data(self, data: Any) -> None: if data is None: - # For some reason, the schema validator does not reject - # "source: null" despite "type" being "object" + # NOTE: The JSON schema does not allow data to be null in this + # context. However, Subiquity bypasses the schema validation when + # a section is set to null. So in practice, we can have data = None + # here. data = {**self.autoinstall_default, "id": None} self.model.search_drivers = data.get("search_drivers", True)