diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb3b07c75d..a2f96955de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,7 @@ repos: files: ^(salt/tests/unit/formulas/.*\.py)$ args: [--strict] additional_dependencies: + - 'pyenchant~=2.0' - pytest - repo: https://github.com/warpnet/salt-lint diff --git a/.pylint-dict b/.pylint-dict index 321844e779..38a2e61334 100644 --- a/.pylint-dict +++ b/.pylint-dict @@ -1,3 +1,4 @@ +API arg basename buildargs diff --git a/buildchain/buildchain/salt_tree.py b/buildchain/buildchain/salt_tree.py index 7d70f6a36e..12a036541b 100644 --- a/buildchain/buildchain/salt_tree.py +++ b/buildchain/buildchain/salt_tree.py @@ -339,6 +339,24 @@ def _get_parts(self) -> Iterator[str]: Path("salt/metalk8s/addons/ui/deployed/dependencies.sls"), Path("salt/metalk8s/addons/ui/deployed/ingress.sls"), Path("salt/metalk8s/addons/ui/deployed/init.sls"), + Path("salt/metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2"), + Path("salt/metalk8s/addons/ui/config/metalk8s-ui-config.yaml"), + targets.TemplateFile( + task_name="salt/metalk8s/addons/ui/config/metalk8s-theme.yaml", + source=constants.ROOT.joinpath( + "salt/metalk8s/addons/ui/config/metalk8s-theme.yaml.in" + ), + destination=constants.ISO_ROOT.joinpath( + "salt/metalk8s/addons/ui/config/metalk8s-theme.yaml" + ), + context={ + "ThemeConfig": textwrap.indent( + UI_THEME_OPTIONS.read_text(encoding="utf-8"), 4 * " " + ) + }, + file_dep=[UI_THEME_OPTIONS], + ), + Path("salt/metalk8s/addons/ui/deployed/ui-configuration.sls"), Path("salt/metalk8s/addons/ui/deployed/files/metalk8s-ui-deployment.yaml.j2"), Path("salt/metalk8s/addons/ui/deployed/namespace.sls"), targets.TemplateFile( @@ -348,12 +366,8 @@ def _get_parts(self) -> Iterator[str]: "salt/metalk8s/addons/ui/deployed/ui.sls" ), context={ - "ThemeConfig": textwrap.indent( - UI_THEME_OPTIONS.read_text(encoding="utf-8"), 12 * " " - ), "ShellUIVersion": versions.SHELL_UI_VERSION, }, - file_dep=[UI_THEME_OPTIONS], ), Path("salt/metalk8s/addons/solutions/deployed/configmap.sls"), Path("salt/metalk8s/addons/solutions/deployed/init.sls"), diff --git a/buildchain/buildchain/shell_ui.py b/buildchain/buildchain/shell_ui.py index 6bca8d3932..7c6baa2a89 100644 --- a/buildchain/buildchain/shell_ui.py +++ b/buildchain/buildchain/shell_ui.py @@ -16,6 +16,7 @@ from buildchain import types from buildchain import utils from buildchain import ui +from buildchain import versions def task_shell_ui() -> types.TaskDict: @@ -54,7 +55,11 @@ def clean() -> None: "_shell_ui_mkdir_build_root", ], "file_dep": list(utils.git_ls("shell-ui")), - "targets": [constants.SHELL_UI_BUILD_ROOT / "index.html"], + "targets": [ + constants.SHELL_UI_BUILD_ROOT.joinpath( + f"solution-ui-navbar.{versions.SHELL_UI_VERSION}.js" + ), + ], "clean": [clean], } diff --git a/docs/developer/architecture/configurations.rst b/docs/developer/architecture/configurations.rst index eabfdd4dd4..e0a2aab331 100644 --- a/docs/developer/architecture/configurations.rst +++ b/docs/developer/architecture/configurations.rst @@ -45,6 +45,10 @@ customize on-site in order to match with my environment specificities: - New Grafana dashboards or new Grafana datasources - Number of replicas for the Prometheus, Alert Manager, Grafana or Dex deployments +- Changing the path on which the MetalK8s UI is deployed +- Customizing the UI theme +- Modifying OIDC provider, client ID or scopes +- Adding custom menu entries .. note:: diff --git a/docs/operation/cluster_and_service_configuration.rst b/docs/operation/cluster_and_service_configuration.rst index 10b7931f55..ae624cc073 100644 --- a/docs/operation/cluster_and_service_configuration.rst +++ b/docs/operation/cluster_and_service_configuration.rst @@ -8,8 +8,8 @@ given Cluster and Services Configurations. Default Service Configurations ------------------------------ -MetalK8s addons (Alertmanager, Dex, Grafana and Prometheus) ships with default -runtime service configurations required for basic service deployment. +MetalK8s addons (Alertmanager, Dex, Grafana, Prometheus and UI) ships with +default runtime service configurations required for basic service deployment. Find below an exhaustive list of available default Service Configurations deployed in a MetalK8s cluster. @@ -86,6 +86,53 @@ The default configuration values for Loki are specified below: :language: yaml :lines: 3- + +UI Default Configuration +~~~~~~~~~~~~~~~~~~~~~~~~ + +MetalK8s UI simplifies management and monitoring of a MetalK8s cluster from a +centralized user interface. + +The default configuration values for MetalK8s UI are specified below: + +.. literalinclude:: ../../salt/metalk8s/addons/ui/config/metalk8s-ui-config.yaml + :language: yaml + :lines: 3- + +See :ref:`csc-ui-customization` to override these defaults. + +UI Default Theme +~~~~~~~~~~~~~~~~ + +You can override theme colors used by MetalK8s UI. + +The default theme for MetalK8s UI are specified below: + +.. literalinclude:: ../../shell-ui/theme.json + :language: json + :lines: 3- + +See :ref:`csc-ui-theme-customization` to override these defaults. + +Shell UI Default Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +MetalK8s Shell UI provides a common set of features to MetalK8s UI and +any other UI (both control and workload plane) configured to include the +Shell UI component(s). +Features exposed include: +- user authentication using an OIDC provider +- navigation menu items, displayed according to user groups +(retrieved from OIDC) + +The default Shell UI configuration values are specified below: + +.. literalinclude:: ../../salt/metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2 + :language: yaml + :lines: 3- + +See :ref:`csc-shell-ui-config-customization` to override these defaults. + Service Configurations Customization ------------------------------------ @@ -548,6 +595,176 @@ edited as follows: Due to internal implementation, ``retention_period`` must be a multiple of ``24h`` in order to get the expected behavior +.. _csc-ui-customization: + +Metalk8s UI Configuration Customization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Default configuration for MetalK8s UI can be overridden by editing its +Cluster and Service ConfigMap ``metalk8s-ui-config`` in namespace +``metalk8s-ui`` under the key ``data.config\.yaml``: + + .. code-block:: shell + + root@bootstrap $ kubectl --kubeconfig /etc/kubernetes/admin.conf \ + edit configmap -n metalk8s-ui \ + metalk8s-ui-config + +Changing the MetalK8s UI Ingress Path +"""""""""""""""""""""""""""""""""""""""""""""""" + +In order to expose another UI at the root path of the control plane, +in place of MetalK8s UI, you need to change the Ingress path from +which MetalK8s UI is served. + +For example, to serve MetalK8s UI at **/platform** instead of **/**, follow +these steps: + +#. Change the value of ``spec.basePath`` in the ConfigMap: + +.. code-block:: yaml + + apiVersion: v1 + kind: ConfigMap + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: UIConfig + spec: + basePath: /platform + +#. Apply your changes by running: + +.. parsed-literal:: + + root\@bootstrap $ kubectl exec -n kube-system -c salt-master \\ + --kubeconfig /etc/kubernetes/admin.conf \\ + salt-master-bootstrap -- salt-run state.sls \\ + metalk8s.addons.ui.deployed saltenv=metalk8s-|version| + +.. _csc-ui-theme-customization: + +MetalK8s UI Theme Customization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Default configuration for MetalK8s UI Theme can be overridden by editing its +Cluster and Service ConfigMap ``metalk8s-theme`` in namespace +``metalk8s-ui`` under the key ``data.config\.yaml``: + + .. code-block:: shell + + root@bootstrap $ kubectl --kubeconfig /etc/kubernetes/admin.conf \ + edit configmap -n metalk8s-ui \ + metalk8s-theme + +Once the theme is edited, apply your changes by running: + + +.. parsed-literal:: + + root\@bootstrap $ kubectl exec -n kube-system -c salt-master \\ + --kubeconfig /etc/kubernetes/admin.conf \\ + salt-master-bootstrap -- salt-run state.sls \\ + metalk8s.addons.ui.deployed saltenv=metalk8s-|version| + +.. _csc-shell-ui-config-customization: + +MetalK8s Shell UI Configuration Customization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Default configuration for MetalK8s Shell UI can be overridden by editing its +Cluster and Service ConfigMap ``metalk8s-shell-ui-config`` in namespace +``metalk8s-ui`` under the key ``data.config\.yaml``. + +Changing UI OIDC Configuration +"""""""""""""""""""""""""""""" + +In order to adapt the OIDC configuration (e.g. the provider URL or +the client ID) used by the UI shareable navigation bar (called Shell UI), +you need to modify its ConfigMap. + +For example, in order to replace the default client ID with "ui", +follow these steps: + +#. Edit the ConfigMap: + +.. code-block:: shell + + root@bootstrap $ kubectl --kubeconfig /etc/kubernetes/admin.conf \ + edit configmap -n metalk8s-ui \ + metalk8s-shell-ui-config + +#. Add the following entry: + +.. code-block:: yaml + + apiVersion: v1 + kind: ConfigMap + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: ShellUIConfig + spec: + # [...] + oidc: + # [...] + clientId: "ui" + +#. Apply your changes by running: + +.. parsed-literal:: + + root\@bootstrap $ kubectl exec -n kube-system -c salt-master \\ + --kubeconfig /etc/kubernetes/admin.conf \\ + salt-master-bootstrap -- salt-run state.sls \\ + metalk8s.addons.ui.deployed saltenv=metalk8s-|version| + +You can similarly edit the requested scopes through the "scopes" attribute or +the OIDC provider URL through the "providerUrl" attribute. + +Changing UI Menu Entries +"""""""""""""""""""""""" + +To change the UI navigation menu entries, follow these steps: + +#. Edit the ConfigMap: + +.. code-block:: shell + + root@bootstrap $ kubectl --kubeconfig /etc/kubernetes/admin.conf \ + edit configmap -n metalk8s-ui \ + metalk8s-shell-ui-config + +#. Edit the ``options`` field. As an example, we add an entry to + the ``main`` section (there is also a ``subLogin`` section): + +.. code-block:: yaml + + apiVersion: v1 + kind: ConfigMap + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: ShellUIConfig + spec: + # [...] + options: + # [...] + main: + # [...] + https://www.scality.com/: + en: "Scality" + fr: "Scality" + +#. Apply your changes by running: + +.. parsed-literal:: + + root\@bootstrap $ kubectl exec -n kube-system -c salt-master \\ + --kubeconfig /etc/kubernetes/admin.conf \\ + salt-master-bootstrap -- salt-run state.sls \\ + metalk8s.addons.ui.deployed saltenv=metalk8s-|version| + Replicas Count Customization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/salt/metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2 b/salt/metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2 new file mode 100644 index 0000000000..d9d6a26758 --- /dev/null +++ b/salt/metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2 @@ -0,0 +1,28 @@ +#!jinja|yaml + +# Defaults for shell UI configuration +apiVersion: addons.metalk8s.scality.com/v1alpha1 +kind: ShellUIConfig +spec: + oidc: + providerUrl: "/oidc" + redirectUrl: "https://{{ grains.metalk8s.control_plane_ip }}:8443/" + clientId: "metalk8s-ui" + responseType: "id_token" + scopes: "openid profile email groups offline_access audience:server:client_id:oidc-auth-client" + userGroupsMapping: + "admin@metalk8s.invalid": + - admin + options: + main: + "https://{{ grains.metalk8s.control_plane_ip }}:8443/": + en: "Platform" + fr: "Plateforme" + "https://{{ grains.metalk8s.control_plane_ip }}:8443/alerts": + en: "Alerts" + fr: "Alertes" + subLogin: + "https://{{ grains.metalk8s.control_plane_ip }}:8443/docs": + en: "Documentation" + fr: "Documentation" + diff --git a/salt/metalk8s/addons/ui/config/metalk8s-theme.yaml.in b/salt/metalk8s/addons/ui/config/metalk8s-theme.yaml.in new file mode 100644 index 0000000000..16bd2e2662 --- /dev/null +++ b/salt/metalk8s/addons/ui/config/metalk8s-theme.yaml.in @@ -0,0 +1,8 @@ +#!yaml + +# Defaults for configuration of MetalK8s UI +apiVersion: addons.metalk8s.scality.com/v1alpha1 +kind: ThemeConfig +spec: + theme: |- +@@ThemeConfig diff --git a/salt/metalk8s/addons/ui/config/metalk8s-ui-config.yaml b/salt/metalk8s/addons/ui/config/metalk8s-ui-config.yaml new file mode 100644 index 0000000000..56d01d08f4 --- /dev/null +++ b/salt/metalk8s/addons/ui/config/metalk8s-ui-config.yaml @@ -0,0 +1,7 @@ +#!yaml + +# Defaults for configuration of MetalK8s UI +apiVersion: addons.metalk8s.scality.com/v1alpha1 +kind: UIConfig +spec: + basePath: / diff --git a/salt/metalk8s/addons/ui/deployed/ingress.sls b/salt/metalk8s/addons/ui/deployed/ingress.sls index e64ca8582a..21809568de 100644 --- a/salt/metalk8s/addons/ui/deployed/ingress.sls +++ b/salt/metalk8s/addons/ui/deployed/ingress.sls @@ -1,4 +1,14 @@ -#! metalk8s_kubernetes +#!jinja | metalk8s_kubernetes + +{%- set metalk8s_ui_defaults = salt.slsutil.renderer( + 'salt://metalk8s/addons/ui/config/metalk8s-ui-config.yaml', saltenv=saltenv + ) +%} + +{%- set metalk8s_ui_config = salt.metalk8s_service_configuration.get_service_conf( + 'metalk8s-ui', 'metalk8s-ui-config', metalk8s_ui_defaults + ) +%} apiVersion: networking.k8s.io/v1beta1 kind: Ingress @@ -76,7 +86,7 @@ spec: rules: - http: paths: - - path: / + - path: {{ metalk8s_ui_config.spec.basePath }} backend: serviceName: metalk8s-ui servicePort: 80 diff --git a/salt/metalk8s/addons/ui/deployed/init.sls b/salt/metalk8s/addons/ui/deployed/init.sls index 8879376ffe..a7eebe861f 100644 --- a/salt/metalk8s/addons/ui/deployed/init.sls +++ b/salt/metalk8s/addons/ui/deployed/init.sls @@ -1,5 +1,6 @@ include: - .namespace - .dependencies +- .ui-configuration - .ui - .ingress diff --git a/salt/metalk8s/addons/ui/deployed/ui-configuration.sls b/salt/metalk8s/addons/ui/deployed/ui-configuration.sls new file mode 100644 index 0000000000..873fe895c4 --- /dev/null +++ b/salt/metalk8s/addons/ui/deployed/ui-configuration.sls @@ -0,0 +1,96 @@ +include: + - .namespace + +{%- set metalk8s_ui_config = salt.metalk8s_kubernetes.get_object( + kind='ConfigMap', + apiVersion='v1', + namespace='metalk8s-ui', + name='metalk8s-ui-config', + ) +%} + +{%- set metalk8s_ui_theme = salt.metalk8s_kubernetes.get_object( + kind='ConfigMap', + apiVersion='v1', + namespace='metalk8s-ui', + name='metalk8s-theme', + ) +%} + +{%- set metalk8s_shell_ui_config = salt.metalk8s_kubernetes.get_object( + kind='ConfigMap', + apiVersion='v1', + namespace='metalk8s-ui', + name='metalk8s-shell-ui-config', + ) +%} + +{%- if metalk8s_ui_config is none %} + +Create metalk8s-ui-config ConfigMap: + metalk8s_kubernetes.object_present: + - manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + name: metalk8s-ui-config + namespace: metalk8s-ui + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: UIConfig + spec: {} + +{%- else %} + +metalk8s-ui-config ConfigMap already exist: + test.succeed_without_changes: [] + +{%- endif %} + + +{%- if metalk8s_ui_theme is none %} + +Create metalk8s-theme ConfigMap: + metalk8s_kubernetes.object_present: + - manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + name: metalk8s-theme + namespace: metalk8s-ui + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: ThemeConfig + spec: {} + +{%- else %} + +metalk8s-theme ConfigMap already exist: + test.succeed_without_changes: [] + +{%- endif %} + +{%- if metalk8s_shell_ui_config is none %} + +Create metalk8s-shell-ui-config ConfigMap: + metalk8s_kubernetes.object_present: + - manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + name: metalk8s-shell-ui-config + namespace: metalk8s-ui + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com/v1alpha1 + kind: ShellUIConfig + spec: {} + +{%- else %} + +metalk8s-shell-ui-config ConfigMap already exist: + test.succeed_without_changes: [] + +{%- endif %} diff --git a/salt/metalk8s/addons/ui/deployed/ui.sls.in b/salt/metalk8s/addons/ui/deployed/ui.sls.in index d25f798c21..475213214c 100644 --- a/salt/metalk8s/addons/ui/deployed/ui.sls.in +++ b/salt/metalk8s/addons/ui/deployed/ui.sls.in @@ -5,6 +5,38 @@ include: import ingress_control_plane with context %} + +{%- set metalk8s_ui_defaults = salt.slsutil.renderer( + 'salt://metalk8s/addons/ui/config/metalk8s-ui-config.yaml', saltenv=saltenv + ) +%} + +{%- set metalk8s_ui_config = salt.metalk8s_service_configuration.get_service_conf( + 'metalk8s-ui', 'metalk8s-ui-config', metalk8s_ui_defaults + ) +%} + + +{%- set metalk8s_theme_defaults = salt.slsutil.renderer( + 'salt://metalk8s/addons/ui/config/metalk8s-theme.yaml', saltenv=saltenv + ) +%} + +{%- set metalk8s_theme = salt.metalk8s_service_configuration.get_service_conf( + 'metalk8s-ui', 'metalk8s-theme', metalk8s_theme_defaults + ) +%} + +{%- set metalk8s_shell_ui_defaults = salt.slsutil.renderer( + 'salt://metalk8s/addons/ui/config/metalk8s-shell-ui-config.yaml.j2', saltenv=saltenv + ) +%} + +{%- set metalk8s_shell_ui_config = salt.metalk8s_service_configuration.get_service_conf( + 'metalk8s-ui', 'metalk8s-shell-ui-config', metalk8s_shell_ui_defaults + ) +%} + Create metalk8s-ui deployment: metalk8s_kubernetes.object_present: - name: salt://{{ slspath }}/files/metalk8s-ui-deployment.yaml.j2 @@ -47,7 +79,8 @@ Create metalk8s-ui ConfigMap: "url_doc": "/docs", "url_alertmanager": "/api/alertmanager", "url_navbar": "/shell/solution-ui-navbar.@@ShellUIVersion.js", - "url_navbar_config": "/shell/config.json" + "url_navbar_config": "/shell/config.json", + "ui_base_path": "{{ metalk8s_ui_config.spec.basePath }}" } Create shell-ui ConfigMap: @@ -59,26 +92,8 @@ Create shell-ui ConfigMap: name: shell-ui namespace: metalk8s-ui data: - config.json: | - { - "docUrl": "/docs", - "oidc": { - "providerUrl": "/oidc", - "redirectUrl": "https://{{ ingress_control_plane }}/", - "clientId": "metalk8s-ui", - "responseType": "id_token", - "scopes": "openid profile email groups offline_access audience:server:client_id:oidc-auth-client" - }, - "options": { - "main": { - "https://{{ ingress_control_plane }}/":{ "en": "Platform", "fr": "Plateforme" }, - "https://{{ ingress_control_plane }}/alerts":{ "en": "Alerts", "fr": "Alertes" } - }, - "subLogin": { - "https://{{ ingress_control_plane }}/docs":{ "en": "Documentation", "fr": "Documentation" } - } - } - } + config.json: |- + {{ metalk8s_shell_ui_config.spec | tojson }} Create ui-branding ConfigMap: metalk8s_kubernetes.object_present: @@ -90,4 +105,4 @@ Create ui-branding ConfigMap: namespace: metalk8s-ui data: theme.json: |- -@@ThemeConfig + {{metalk8s_theme.spec.theme | indent(12, False) }} diff --git a/salt/metalk8s/service-configuration/deployed/init.sls b/salt/metalk8s/service-configuration/deployed/init.sls index 6e6b2f4572..6dc3ff3699 100644 --- a/salt/metalk8s/service-configuration/deployed/init.sls +++ b/salt/metalk8s/service-configuration/deployed/init.sls @@ -9,3 +9,4 @@ include: - metalk8s.addons.prometheus-operator.deployed.service-configuration - metalk8s.addons.dex.deployed.service-configuration - metalk8s.addons.logging.loki.deployed.service-configuration + - metalk8s.addons.ui.deployed.ui-configuration diff --git a/salt/tests/unit/formulas/conftest.py b/salt/tests/unit/formulas/conftest.py index 569530b8f3..5b508bee4b 100644 --- a/salt/tests/unit/formulas/conftest.py +++ b/salt/tests/unit/formulas/conftest.py @@ -6,6 +6,7 @@ from tests.unit.formulas.fixtures.data import fixture_base_grains from tests.unit.formulas.fixtures.data import fixture_base_kubernetes from tests.unit.formulas.fixtures.data import fixture_base_pillar +from tests.unit.formulas.fixtures.data import fixture_buildchain_template_context from tests.unit.formulas.fixtures.data import fixture_metalk8s_versions from tests.unit.formulas.fixtures.environment import fixture_template_loader from tests.unit.formulas.fixtures.environment import fixture_environment diff --git a/salt/tests/unit/formulas/fixtures/data.py b/salt/tests/unit/formulas/fixtures/data.py index eaaa9c0543..f5af3c162c 100644 --- a/salt/tests/unit/formulas/fixtures/data.py +++ b/salt/tests/unit/formulas/fixtures/data.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Any import sys +import textwrap import pytest import yaml @@ -41,6 +42,26 @@ def fixture_metalk8s_versions() -> Any: return versions.SALT_VERSIONS_JSON +@pytest.fixture(scope="session", name="buildchain_template_context") +def fixture_buildchain_template_context() -> Any: + """Emulate .in template context for buildchain.""" + buildchain_path = paths.REPO_ROOT / "buildchain" + sys.path.insert(0, str(buildchain_path)) + # pylint: disable=import-error,import-outside-toplevel + from buildchain import versions + + # pylint: enable=import-error,import-outside-toplevel + + sys.path.pop(0) + ui_theme_options: Path = paths.REPO_ROOT / "shell-ui" / "theme.json" + return { + "VERSION": versions.VERSION, + "ThemeConfig": textwrap.indent( + ui_theme_options.read_text(encoding="utf-8"), 4 * " " + ), + } + + @pytest.fixture(scope="session", name="base_kubernetes") def fixture_base_kubernetes() -> kubernetes.K8sData: """Return a basic dataset for mocking Kubernetes API. diff --git a/salt/tests/unit/formulas/fixtures/environment.py b/salt/tests/unit/formulas/fixtures/environment.py index f1f3f5039f..9b59a64e37 100644 --- a/salt/tests/unit/formulas/fixtures/environment.py +++ b/salt/tests/unit/formulas/fixtures/environment.py @@ -1,5 +1,6 @@ """Fixtures for setting up a Jinja rendering environment.""" import json +from string import Template from typing import Any, Callable, Dict, Tuple import jinja2 @@ -11,15 +12,28 @@ ) import salt.utils.jinja # type: ignore + from tests.unit.formulas import paths +class BuildchainTemplate(Template): + """Template class using @@ as a delimiter.""" + + delimiter = "@@" + + class MockedFSLoader(jinja2.FileSystemLoader): """A FilesystemLoader with overrides for arbitrary templates.""" - def __init__(self, directory: str, mock_templates: Dict[str, str]): + def __init__( + self, + directory: str, + mock_templates: Dict[str, str], + buildchain_template_context: Dict[str, str], + ): super().__init__(directory) self._mock_templates = mock_templates + self._buildchain_template_context = buildchain_template_context def get_source( self, environment: jinja2.Environment, template: str @@ -27,16 +41,32 @@ def get_source( mock_template = self._mock_templates.get(template) if mock_template is not None: return mock_template, template, lambda: None - return super().get_source(environment, template) + try: + return super().get_source(environment, template) + except jinja2.TemplateNotFound: + # If template is not found we check if a .in template + # exists and use a mocked context + source, path, is_up_to_date = super().get_source( + environment, template + ".in" + ) + source_buildchain_template = BuildchainTemplate(source) + new_source = source_buildchain_template.substitute( + self._buildchain_template_context + ) + return new_source, path, is_up_to_date @pytest.fixture(scope="session", name="template_loader") -def fixture_template_loader(metalk8s_versions: Any) -> jinja2.FileSystemLoader: +def fixture_template_loader( + metalk8s_versions: Any, buildchain_template_context: Dict[str, str] +) -> jinja2.FileSystemLoader: """Load templates using the salt/ directory as a root.""" mock_templates = { "metalk8s/versions.json": json.dumps(metalk8s_versions), } - return MockedFSLoader(str(paths.SALT_DIR), mock_templates) + return MockedFSLoader( + str(paths.SALT_DIR), mock_templates, buildchain_template_context + ) @pytest.fixture(scope="session", name="environment")