From e6b87684349b753dffbf90bc677b3290b8f31b27 Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Wed, 24 Nov 2021 16:22:59 +0100 Subject: [PATCH 1/5] eve: Add hard podAntiAffinity on DNS in bootstrap restore --- eve/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/eve/main.yml b/eve/main.yml index 723b61adae..181a0216e8 100644 --- a/eve/main.yml +++ b/eve/main.yml @@ -2491,7 +2491,12 @@ stages: - ShellCommand: *check_ssh_config_bootstrap - ShellCommand: *copy_bastion_priv_key_to_bootstrap - ShellCommand: *set_bootstrap_minion_id_ssh - - ShellCommand: *bootstrap_config_ssh + - ShellCommand: + <<: *bootstrap_config_ssh + env: + <<: *_env_bootstrap_config_ssh + COREDNS_ANTI_AFFINITY: >- + {"hard": [{"topologyKey": "kubernetes.io/hostname"}]} - ShellCommand: *copy_iso_bootstrap_ssh - ShellCommand: *create_mountpoint_ssh - ShellCommand: *mount_iso_ssh From 00e578785bc8d3f4e42d53e9eae818dfd82bfbef Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Wed, 24 Nov 2021 13:45:40 +0100 Subject: [PATCH 2/5] charts/render.py: Add `var_quoted` to remove quote if needed In some helm chart, some value are rendered as YAML but we want to use a salt function in jinja to "compute" this yaml part, since it's rendered as YAML the resulting SLS will the salt execution module quoted, which result in an invalid YAML --- charts/render.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/charts/render.py b/charts/render.py index c841dca51b..7a72a92f36 100755 --- a/charts/render.py +++ b/charts/render.py @@ -158,6 +158,13 @@ def replace_magic_strings(rendered_yaml): rendered_yaml, ) + # Handle __var_quoted__ + result = re.sub( + r"'__var_quoted__\((?P[^']*)\)'", + r"{% endraw -%}{{ \g }}{%- raw %}", + result, + ) + # Handle __var_tojson__ result = re.sub( r"__var_tojson__\((?P[\w\-_]+(?:\.[\w\-_()|]+)*)\)", From 9e1f5fca43ed5d73e03c45d90453c64cea77d3e6 Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Wed, 24 Nov 2021 13:48:26 +0100 Subject: [PATCH 3/5] salt: Add execution module to "compute" affinity Since we want to expose a really simple way to setup Pod affinity we do not use the exact same syntax as what need to provided in the Kubernetes objects, in order to convert from "simple syntax" to "kubernetes syntax" we add this execution module See: #3574 --- .../metalk8s_service_configuration.py | 63 ++++++++++++++ .../test_metalk8s_service_configuration.yaml | 87 +++++++++++++++++++ .../test_metalk8s_service_configuration.py | 15 +++- 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/salt/_modules/metalk8s_service_configuration.py b/salt/_modules/metalk8s_service_configuration.py index 2a48b67afe..c2f9cf8fde 100644 --- a/salt/_modules/metalk8s_service_configuration.py +++ b/salt/_modules/metalk8s_service_configuration.py @@ -95,3 +95,66 @@ def get_service_conf( ) return merged_config + + +def get_pod_affinity(affinities, label_selector, namespaces): + """Convert human readable affinity to real Kubernetes affinity + + NOTE: This function only handle podAntiAffinity for the moment + + Input look like + ```yaml + podAntiAffinity: + hard: + - topologyKey: kubernetes.io/hostname + soft: + - topologyKey: my.topology.key/important + weight: 100 + - topologyKey: my.topology.key/a.bit.less.important + ``` + + Args: + value (dict): Simple dict affinity + label_selector (dict): Dict for label selector to match Pods for this affinity + namespaces (list): List of namespaces where those Pods sits + + Returns: + A dict that can be used as `affinity` in a Kubernetes object + """ + if not isinstance(namespaces, list): + namespaces = [namespaces] + + result = {} + res_pod_anti_affinity = {} + + pod_anti_affinity = (affinities or {}).get("podAntiAffinity") or {} + + for soft_affinity in pod_anti_affinity.get("soft") or []: + res_pod_anti_affinity.setdefault( + "preferredDuringSchedulingIgnoredDuringExecution", [] + ).append( + { + "weight": soft_affinity.get("weight", 1), + "podAffinityTerm": { + "labelSelector": {"matchLabels": label_selector}, + "namespaces": namespaces, + "topologyKey": soft_affinity["topologyKey"], + }, + } + ) + + for hard_affinity in pod_anti_affinity.get("hard") or []: + res_pod_anti_affinity.setdefault( + "requiredDuringSchedulingIgnoredDuringExecution", [] + ).append( + { + "labelSelector": {"matchLabels": label_selector}, + "namespaces": namespaces, + "topologyKey": hard_affinity["topologyKey"], + } + ) + + if res_pod_anti_affinity: + result["podAntiAffinity"] = res_pod_anti_affinity + + return result diff --git a/salt/tests/unit/modules/files/test_metalk8s_service_configuration.yaml b/salt/tests/unit/modules/files/test_metalk8s_service_configuration.yaml index 14c2350942..bd5a7d41aa 100644 --- a/salt/tests/unit/modules/files/test_metalk8s_service_configuration.yaml +++ b/salt/tests/unit/modules/files/test_metalk8s_service_configuration.yaml @@ -150,3 +150,90 @@ get_service_config: kind: my_addon_kind result: "Expected value my_kind for key kind, got my_addon_kind" raises: True + +get_pod_affinity: + # Success: Nominal no affinity + - affinities: {} + result: {} + - affinities: null + result: {} + - affinities: + podAntiAffinity: {} + result: {} + - affinities: + podAntiAffinity: null + result: {} + - affinities: + podAntiAffinity: + hard: null + soft: null + result: {} + - affinities: + podAntiAffinity: + hard: [] + soft: [] + result: {} + - affinities: + podAntiAffinity: + hard: null + result: {} + + # Success: Nominal simple soft pod anti affinity on hostname + - &nominal_soft_anti_affinity + affinities: + podAntiAffinity: + soft: + - topologyKey: kubernetes.io/hostname + result: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - &hostname_soft_affinity + weight: 1 + podAffinityTerm: &default_pod_affinity_term + labelSelector: + matchLabels: + app: my-app + namespaces: + - my-namespace + topologyKey: kubernetes.io/hostname + + # Success: nominal, namespace as string + - <<: *nominal_soft_anti_affinity + namespaces: "my-namespace" + + # Success: Hard pod anti affinity on hostname + - affinities: + podAntiAffinity: + hard: + - topologyKey: kubernetes.io/hostname + result: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - *default_pod_affinity_term + + # Success: Soft and Hard pod anti affinities + - affinities: + podAntiAffinity: + soft: + - topologyKey: "my-first-soft-key" + - topologyKey: "my-second-soft-key" + weight: 42 + hard: + - topologyKey: "my-first-hard-key" + - topologyKey: "my-second-hard-key" + result: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + <<: *default_pod_affinity_term + topologyKey: "my-first-soft-key" + - weight: 42 + podAffinityTerm: + <<: *default_pod_affinity_term + topologyKey: "my-second-soft-key" + requiredDuringSchedulingIgnoredDuringExecution: + - <<: *default_pod_affinity_term + topologyKey: "my-first-hard-key" + - <<: *default_pod_affinity_term + topologyKey: "my-second-hard-key" diff --git a/salt/tests/unit/modules/test_metalk8s_service_configuration.py b/salt/tests/unit/modules/test_metalk8s_service_configuration.py index 934e14909f..5e11b95b91 100644 --- a/salt/tests/unit/modules/test_metalk8s_service_configuration.py +++ b/salt/tests/unit/modules/test_metalk8s_service_configuration.py @@ -8,7 +8,7 @@ from _modules import metalk8s_service_configuration -from tests.unit import mixins +from tests.unit import mixins, utils YAML_TESTS_FILE = os.path.join( @@ -90,3 +90,16 @@ def test_get_service_conf( result, ) get_configmap_mock.assert_called_once() + + @utils.parameterized_from_cases(YAML_TESTS_CASES["get_pod_affinity"]) + def test_get_pod_affinity(self, result, **kwargs): + """ + Tests the return of `get_pod_affinity` function + """ + if not kwargs.get("label_selector"): + kwargs["label_selector"] = {"app": "my-app"} + if not kwargs.get("namespaces"): + kwargs["namespaces"] = ["my-namespace"] + self.assertEqual( + metalk8s_service_configuration.get_pod_affinity(**kwargs), result + ) From 0914eaaf78e4947f540b8296407c9dd3e7dfefc7 Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Wed, 24 Nov 2021 16:28:38 +0100 Subject: [PATCH 4/5] salt: Use `get_pod_affinity` for CoreDNS and move it to `affinity` Instead of computing the PodAntiAffinity from pillar directly in the SLS as jinja use the dedicated function `get_pod_affinity` for it. In order to allow support in the future for other type of `affinity` add an extra `affinity` layer in the config --- docs/installation/bootstrap.rst | 16 ++++--- eve/main.yml | 12 ++--- salt/metalk8s/kubernetes/coredns/deployed.sls | 42 ++++-------------- salt/tests/unit/formulas/config.yaml | 44 ++++++++++--------- salt/tests/unit/formulas/fixtures/salt.py | 6 +++ 5 files changed, 54 insertions(+), 66 deletions(-) diff --git a/docs/installation/bootstrap.rst b/docs/installation/bootstrap.rst index f41351a84a..e8a79c7508 100644 --- a/docs/installation/bootstrap.rst +++ b/docs/installation/bootstrap.rst @@ -72,10 +72,11 @@ Configuration : True coreDNS: replicas: 2 - podAntiAffinity: - hard: [] - soft: - - topologyKey: kubernetes.io/hostname + affinity: + podAntiAffinity: + hard: [] + soft: + - topologyKey: kubernetes.io/hostname The ``networks`` field specifies a range of IP addresses written in CIDR notation for it's various subfields. @@ -184,9 +185,10 @@ defaults kubernetes configuration. kubernetes: coreDNS: - podAntiAffinity: - hard: - - topologyKey: kubernetes.io/hostname + affinity: + podAntiAffinity: + hard: + - topologyKey: kubernetes.io/hostname .. _Feature Gates: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ diff --git a/eve/main.yml b/eve/main.yml index 181a0216e8..ad8b07f429 100644 --- a/eve/main.yml +++ b/eve/main.yml @@ -346,7 +346,7 @@ models: env: &_env_bootstrap_config_ssh DEBUG: "%(prop:metalk8s_debug:-false)s" ARCHIVE: metalk8s.iso - COREDNS_ANTI_AFFINITY: "{}" + COREDNS_AFFINITY: "{}" command: | ssh -F ssh_config bootstrap " sudo bash << EOF @@ -370,7 +370,7 @@ models: debug: ${DEBUG} kubernetes: coreDNS: - podAntiAffinity: ${COREDNS_ANTI_AFFINITY} + affinity: ${COREDNS_AFFINITY} END EOF" haltOnFailure: true @@ -2359,8 +2359,8 @@ stages: env: <<: *_env_bootstrap_config_ssh ARCHIVE: /archives/metalk8s.iso - COREDNS_ANTI_AFFINITY: >- - {"hard": [{"topologyKey": "kubernetes.io/hostname"}]} + COREDNS_AFFINITY: >- + {"podAntiAffinity": {"hard": [{"topologyKey": "kubernetes.io/hostname"}]}} - ShellCommand: <<: *copy_iso_bootstrap_ssh env: @@ -2495,8 +2495,8 @@ stages: <<: *bootstrap_config_ssh env: <<: *_env_bootstrap_config_ssh - COREDNS_ANTI_AFFINITY: >- - {"hard": [{"topologyKey": "kubernetes.io/hostname"}]} + COREDNS_AFFINITY: >- + {"podAntiAffinity": {"hard": [{"topologyKey": "kubernetes.io/hostname"}]}} - ShellCommand: *copy_iso_bootstrap_ssh - ShellCommand: *create_mountpoint_ssh - ShellCommand: *mount_iso_ssh diff --git a/salt/metalk8s/kubernetes/coredns/deployed.sls b/salt/metalk8s/kubernetes/coredns/deployed.sls index b67c760c9f..bd4a9475b1 100644 --- a/salt/metalk8s/kubernetes/coredns/deployed.sls +++ b/salt/metalk8s/kubernetes/coredns/deployed.sls @@ -8,41 +8,17 @@ {%- set replicas = pillar_coredns.get("replicas") or 2 %} {%- set label_selector = {"k8s-app": "kube-dns"} %} -{%- set pillar_affinities = pillar_coredns.get("podAntiAffinity", {}) %} +{%- set pillar_affinities = pillar_coredns.get("affinity", {}) %} {#- NOTE: The default podAntiAffinity is a soft anti-affinity on hostname #} -{%- set soft_affinities = pillar_affinities.get("soft") or [{"topologyKey": "kubernetes.io/hostname"}] %} -{%- set hard_affinities = pillar_affinities.get("hard") or [] %} - -{%- set affinity = { - "podAntiAffinity": {} -} %} - -{%- for soft_affinity in soft_affinities %} - {%- do affinity["podAntiAffinity"].setdefault( - "preferredDuringSchedulingIgnoredDuringExecution", [] - ).append({ - "weight": soft_affinity.get("weight", 1), - "podAffinityTerm": { - "labelSelector": { - "matchLabels": label_selector - }, - "namespaces": ["kube-system"], - "topologyKey": soft_affinity["topologyKey"] - } - }) %} -{%- endfor %} -{%- for hard_affinity in hard_affinities %} - {%- do affinity["podAntiAffinity"].setdefault( - "requiredDuringSchedulingIgnoredDuringExecution", [] - ).append({ - "labelSelector": { - "matchLabels": label_selector - }, - "namespaces": ["kube-system"], - "topologyKey": hard_affinity["topologyKey"] - }) %} -{%- endfor %} +{%- do pillar_affinities.setdefault("podAntiAffinity", {}).setdefault( + "soft", [{"topologyKey": "kubernetes.io/hostname"}] +) %} +{%- set affinity = salt.metalk8s_service_configuration.get_pod_affinity( + pillar_affinities, + label_selector, + "kube-system" +) %} Create coredns ConfigMap: metalk8s_kubernetes.object_present: diff --git a/salt/tests/unit/formulas/config.yaml b/salt/tests/unit/formulas/config.yaml index 2763f007b8..e7993cdb6f 100644 --- a/salt/tests/unit/formulas/config.yaml +++ b/salt/tests/unit/formulas/config.yaml @@ -189,38 +189,42 @@ metalk8s: pillar_overrides: kubernetes: coreDNS: - podAntiAffinity: - soft: - - topologyKey: kubernetes.io/hostname - - topologyKey: kubernetes.io/zone - weight: 10 + affinity: + podAntiAffinity: + soft: + - topologyKey: kubernetes.io/hostname + - topologyKey: kubernetes.io/zone + weight: 10 "Hard anti-affinity on hostname": pillar_overrides: kubernetes: coreDNS: - podAntiAffinity: - hard: - - topologyKey: kubernetes.io/hostname + affinity: + podAntiAffinity: + hard: + - topologyKey: kubernetes.io/hostname "Multiple hard anti-affinity": pillar_overrides: kubernetes: coreDNS: - podAntiAffinity: - hard: - - topologyKey: kubernetes.io/hostname - - topologyKey: kubernetes.io/zone + affinity: + podAntiAffinity: + hard: + - topologyKey: kubernetes.io/hostname + - topologyKey: kubernetes.io/zone "Multiple hard and soft anti-affinity": pillar_overrides: kubernetes: coreDNS: - podAntiAffinity: - soft: - - topologyKey: kubernetes.io/hostname - - topologyKey: kubernetes.io/zone - weight: 10 - hard: - - topologyKey: kubernetes.io/hostname - - topologyKey: kubernetes.io/zone + affinity: + podAntiAffinity: + soft: + - topologyKey: kubernetes.io/hostname + - topologyKey: kubernetes.io/zone + weight: 10 + hard: + - topologyKey: kubernetes.io/hostname + - topologyKey: kubernetes.io/zone files: coredns-deployment.yaml.j2: diff --git a/salt/tests/unit/formulas/fixtures/salt.py b/salt/tests/unit/formulas/fixtures/salt.py index 63723107d1..0c2a4b8105 100644 --- a/salt/tests/unit/formulas/fixtures/salt.py +++ b/salt/tests/unit/formulas/fixtures/salt.py @@ -9,6 +9,8 @@ from typing import Any, Callable, Dict, List, Optional, Type from unittest.mock import MagicMock +from _modules import metalk8s_service_configuration # type: ignore + import jinja2 import pytest import salt.utils.data # type: ignore @@ -448,6 +450,10 @@ def metalk8s_network_get_ip_from_cidrs( register_basic("metalk8s_service_configuration.get_service_conf")( lambda _namespace, _name, defaults: defaults ) +# NOTE: This is a static function that does not rely on any salt stuff +register_basic("metalk8s_service_configuration.get_pod_affinity")( + metalk8s_service_configuration.get_pod_affinity +) # Used in metalk8s.salt.master.installed to mount Solution ISOs in the salt-master Pod. register_basic("metalk8s_solutions.list_available")(MagicMock(return_value={})) From 5edb9003b1545243e8ca5f9e01f5987b4ca1b1ac Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Tue, 23 Nov 2021 15:48:16 +0100 Subject: [PATCH 5/5] charts,salt,tests: Add `podAntiAffinity` support for Dex This commit add ability to configure `podAntiAffinity` for Dex from CSC. Patche the Dex helm chart to add support for `strategy` on Dex deployment, as the default one does not make sense for our Dex deployment See https://github.com/dexidp/helm-charts/issues/66 Render chart to salt state using ``` ./charts/render.py dex charts/dex.yaml charts/dex \ --namespace metalk8s-auth \ --service-config dex metalk8s-dex-config \ metalk8s/addons/dex/config/dex.yaml.j2 metalk8s-auth \ > salt/metalk8s/addons/dex/deployed/chart.sls ``` See: #3574 --- CHANGELOG.md | 5 +++++ charts/dex.yaml | 13 +++++++++++++ charts/dex/templates/deployment.yaml | 4 ++++ salt/metalk8s/addons/dex/config/dex.yaml.j2 | 8 ++++++++ salt/metalk8s/addons/dex/deployed/chart.sls | 7 +++++++ tests/post/features/service_configuration.feature | 10 ++++++++++ tests/post/steps/test_service_configuration.py | 10 ++++++++++ 7 files changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8102101db..35a4000447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ hostname, so that if it's possible each `CoreDNS` pods will sit on different infra node (PR[#3579](https://github.com/scality/metalk8s/pull/3579)) +- Allow to manage soft and hard `podAntiAffinity` for `Dex` from Cluster + and Services Configurations, with a default soft anti-affinity on hostname, + so that if it's possible each `Dex` pods will sit on a different infra node + (PR[#3614](https://github.com/scality/metalk8s/pull/3614)) + ### Removals - Removed the PDF support for documentation, replaced it with the HTML output diff --git a/charts/dex.yaml b/charts/dex.yaml index 5e0a1bbe20..921384ca1e 100644 --- a/charts/dex.yaml +++ b/charts/dex.yaml @@ -14,6 +14,19 @@ tolerations: replicaCount: '__var__(dex.spec.deployment.replicas)' +strategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + + +# NOTE: We use `__var_quoted__` as `affinity` is rendered as YAML +# so result will be quoted +affinity: '__var_quoted__(salt.metalk8s_service_configuration.get_pod_affinity( + dex.spec.deployment.affinity, + {"app.kubernetes.io/instance": "dex", "app.kubernetes.io/name": "dex"}, + "metalk8s-auth"))' + # grpc support grpc: enabled: false diff --git a/charts/dex/templates/deployment.yaml b/charts/dex/templates/deployment.yaml index 386a43f7ea..3a99b40242 100644 --- a/charts/dex/templates/deployment.yaml +++ b/charts/dex/templates/deployment.yaml @@ -11,6 +11,10 @@ spec: selector: matchLabels: {{- include "dex.selectorLabels" . | nindent 6 }} + {{- with .Values.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} template: metadata: annotations: diff --git a/salt/metalk8s/addons/dex/config/dex.yaml.j2 b/salt/metalk8s/addons/dex/config/dex.yaml.j2 index 3acb71d177..d1403897a8 100644 --- a/salt/metalk8s/addons/dex/config/dex.yaml.j2 +++ b/salt/metalk8s/addons/dex/config/dex.yaml.j2 @@ -20,6 +20,14 @@ spec: # Deployment configuration deployment: replicas: 2 + affinity: + podAntiAffinity: + soft: + - topologyKey: kubernetes.io/hostname + # - topologyKey: my.second.important/topologyKey + # weight: 42 + # hard: + # - topologyKey: kubernetes.io/hostname # Dex server configuration config: diff --git a/salt/metalk8s/addons/dex/deployed/chart.sls b/salt/metalk8s/addons/dex/deployed/chart.sls index db4c7a4042..315014ecad 100644 --- a/salt/metalk8s/addons/dex/deployed/chart.sls +++ b/salt/metalk8s/addons/dex/deployed/chart.sls @@ -124,6 +124,10 @@ spec: matchLabels: app.kubernetes.io/instance: dex app.kubernetes.io/name: dex + strategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate template: metadata: annotations: @@ -133,6 +137,9 @@ spec: app.kubernetes.io/instance: dex app.kubernetes.io/name: dex spec: + affinity: {% endraw -%}{{ salt.metalk8s_service_configuration.get_pod_affinity( + dex.spec.deployment.affinity, {"app.kubernetes.io/instance": "dex", "app.kubernetes.io/name": + "dex"}, "metalk8s-auth") }}{%- raw %} containers: - args: - dex diff --git a/tests/post/features/service_configuration.feature b/tests/post/features/service_configuration.feature index f885c3a476..f39b8e0ba5 100644 --- a/tests/post/features/service_configuration.feature +++ b/tests/post/features/service_configuration.feature @@ -29,3 +29,13 @@ Feature: Cluster and Services Configurations When we update the CSC 'spec.rules.node_exporter.node_filesystem_space_filling_up.warning.hours' to '48' And we apply the 'metalk8s.addons.prometheus-operator.deployed' state Then we have an alert rule 'NodeFilesystemSpaceFillingUp' in group 'node-exporter' with severity 'warning' and 'annotations.summary' equal to 'Filesystem is predicted to run out of space within the next 48 hours.' + + Scenario: Dex pods spreading + Given the Kubernetes API is available + And we are on a multi node cluster + And we have a 'metalk8s-dex-config' CSC in namespace 'metalk8s-auth' + When we update the CSC 'spec.deployment.affinity' to '{"podAntiAffinity": {"hard": [{"topologyKey": "kubernetes.io/hostname"}]}}' + And we apply the 'metalk8s.addons.dex.deployed' state + And we wait for the rollout of 'deploy/dex' in namespace 'metalk8s-auth' to complete + Then pods with label 'app.kubernetes.io/name=dex' are 'Ready' + And each pods with label 'app.kubernetes.io/name=dex' are on a different node diff --git a/tests/post/steps/test_service_configuration.py b/tests/post/steps/test_service_configuration.py index 94048b82d0..f4fb97ae2e 100644 --- a/tests/post/steps/test_service_configuration.py +++ b/tests/post/steps/test_service_configuration.py @@ -33,6 +33,14 @@ def test_prometheus_rules_customization(host): pass +@scenario( + "../features/service_configuration.feature", + "Dex pods spreading", +) +def test_dex_spread(host): + pass + + # }}} # Given {{{ @@ -65,6 +73,8 @@ def csc(host, ssh_config, version, k8s_client, name, namespace): @when(parsers.parse("we update the CSC '{path}' to '{value}'")) def update_csc(csc, path, value): + value = yaml.safe_load(value) + csc_content = csc.get() utils.set_dict_element(csc_content, path, value) csc.update(csc_content, apply_config=False)