From 8ef2221ce17d804733d2b05cce8baac68127ccf0 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 30 Sep 2024 12:44:05 +0200 Subject: [PATCH] example: allow user modifications via `otk.define` This commit adds support for user modifications. The idea behind modifications is that those are variables that users want to change to customize the imagess. This appears uses the existing otk.defines mechanism and adds a convention around the naming for modifications. We will use the customizations from blueprints and map them to modifcations. A simple example would be: ```yaml otk.define: user: modifications: language: nl_NL.UTF-8 ``` In our otk files we will have defaults for all supported modifications and then join the defaults with the user define modifcations. ```yaml otk.define: default: modifications: language: en_US.UTF-8 modifications: otk.op.join: values: - ${default.modifications} - ${user.modifications} ``` This allows us to follow the idea from the images PR#797 (https://github.com/osbuild/images/pull/797): ```yaml otk.define: user: modifications: hostname: "hal-9000" kernel_append: "debug" keyboard: "uk" otk.include: `/otkroot/fedora-40/x86_64/qcow2.yaml` ``` and because the otk.op.join will merge two tree this should work. We could also (later) provide a convinience helper like: ``` $ otk compile --preload/--include "my-customizatins.yaml" centos-9-x86_64-ami.yaml ``` which would just preload/include (terms stawman) the customizations and then proceed processing the centos-9-x86_64-ami.yaml file. To see what modifications are supported by a given image we would just list what "modifications" vars are being used for a given image type. Note that this commit cheats by pre-defining an empty "user.modifications" var. This avoids having to deal with the issue of undefined variables and conditionals. --- example/centos/centos-9-x86_64-ami.yaml | 16 ++++++++++++++-- src/otk/document.py | 2 ++ src/otk/transform.py | 8 +++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/example/centos/centos-9-x86_64-ami.yaml b/example/centos/centos-9-x86_64-ami.yaml index 986723e6..1404ecbc 100644 --- a/example/centos/centos-9-x86_64-ami.yaml +++ b/example/centos/centos-9-x86_64-ami.yaml @@ -1,9 +1,21 @@ otk.version: "1" otk.define: + default: + modifications: + language: en_US.UTF-8 + filesystem: + filename: "image.raw" + + modifications: + otk.op.join: + values: + - ${default.modifications} + - ${user.modifications} + filesystem: modifications: - filename: "image.raw" + ${modifications.filesystem} packages: build: otk.external.osbuild-gen-depsolve-dnf4: @@ -134,7 +146,7 @@ otk.target.osbuild: prefix: '' - type: org.osbuild.locale options: - language: en_US.UTF-8 + language: ${modifications.language} - type: org.osbuild.keymap options: keymap: us diff --git a/src/otk/document.py b/src/otk/document.py index 14e94ed4..a93ebab5 100644 --- a/src/otk/document.py +++ b/src/otk/document.py @@ -21,6 +21,8 @@ class Omnifest: def __init__(self, path: pathlib.Path, target: str = "", *, warn_duplicated_defs: bool = False) -> None: self._ctx = CommonContext(target_requested=target, warn_duplicated_defs=warn_duplicated_defs) + # XXX: this can be removed once we find a way to deal with unset variables + self._ctx.define("user.modifications", {}) self._target = target # XXX: redo using a type-safe target registry if target: diff --git a/src/otk/transform.py b/src/otk/transform.py index ada960d2..9f659ea7 100644 --- a/src/otk/transform.py +++ b/src/otk/transform.py @@ -222,7 +222,7 @@ def op(ctx: Context, tree: Any, key: str) -> Any: @tree.must_be(dict) @tree.must_pass(tree.has_keys(["values"])) -def op_join(_: Context, tree: dict[str, Any]) -> Any: +def op_join(ctx: Context, tree: dict[str, Any]) -> Any: """Join a map/seq.""" values = tree["values"] @@ -230,6 +230,10 @@ def op_join(_: Context, tree: dict[str, Any]) -> Any: raise TransformDirectiveTypeError( f"seq join received values of the wrong type, was expecting a list of lists but got {values!r}") + for i, val in enumerate(values): + if isinstance(val, str): + values[i] = substitute_vars(ctx, val) + if all(isinstance(sl, list) for sl in values): return _op_seq_join(values) if all(isinstance(sl, dict) for sl in values): @@ -257,6 +261,8 @@ def _op_map_join(values: List[dict]) -> Any: result = {} + # XXX: pretty sure this will need to become recursive *or* we need + # something like a "merge_strategy" in "otk.op.join" for value in values: result.update(value)