From c073eea5b3779f3f41ba4be88347d6df20684c65 Mon Sep 17 00:00:00 2001 From: Bikouo Aubin <79859644+abikouo@users.noreply.github.com> Date: Thu, 15 Dec 2022 19:17:17 +0100 Subject: [PATCH] k8s - fix issue with server side apply (#549) k8s - fix issue with server side apply SUMMARY Fix #548 and #547 ISSUE TYPE Bugfix Pull Request Reviewed-by: Mike Graves Reviewed-by: Bikouo Aubin --- .../fragments/549-fix-server-side-apply.yaml | 4 ++ plugins/module_utils/apply.py | 13 ++++- .../targets/k8s_apply/tasks/main.yml | 58 +++++++++++++++++++ .../k8s_apply/tasks/server_side_apply.yml | 24 ++++++++ .../test_inventory_read_credentials.py | 28 +++------ 5 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 changelogs/fragments/549-fix-server-side-apply.yaml create mode 100644 tests/integration/targets/k8s_apply/tasks/server_side_apply.yml diff --git a/changelogs/fragments/549-fix-server-side-apply.yaml b/changelogs/fragments/549-fix-server-side-apply.yaml new file mode 100644 index 0000000000..099080df6b --- /dev/null +++ b/changelogs/fragments/549-fix-server-side-apply.yaml @@ -0,0 +1,4 @@ +--- +bugfixes: + - k8s - Fix issue with server side apply with kubernetes release '25.3.0' (https://github.com/ansible-collections/kubernetes.core/issues/548). + - k8s - Fix issue with check_mode when using server side apply (https://github.com/ansible-collections/kubernetes.core/issues/547). diff --git a/plugins/module_utils/apply.py b/plugins/module_utils/apply.py index 29e164a134..dea185ef3e 100644 --- a/plugins/module_utils/apply.py +++ b/plugins/module_utils/apply.py @@ -24,6 +24,13 @@ from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ( ApplyException, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + gather_versions, +) +from ansible_collections.kubernetes.core.plugins.module_utils.version import ( + LooseVersion, +) + try: from kubernetes.dynamic.exceptions import NotFoundError @@ -131,7 +138,10 @@ def k8s_apply(resource, definition, **kwargs): existing, desired = apply_object(resource, definition) server_side = kwargs.get("server_side", False) if server_side: - body = json.dumps(definition).encode() + versions = gather_versions() + body = definition + if LooseVersion(versions["kubernetes"]) < LooseVersion("25.0.0"): + body = json.dumps(definition).encode() # server_side_apply is forces content_type to 'application/apply-patch+yaml' return resource.server_side_apply( body=body, @@ -139,6 +149,7 @@ def k8s_apply(resource, definition, **kwargs): namespace=definition["metadata"].get("namespace"), force_conflicts=kwargs.get("force_conflicts"), field_manager=kwargs.get("field_manager"), + dry_run=kwargs.get("dry_run"), ) if not existing: return resource.create( diff --git a/tests/integration/targets/k8s_apply/tasks/main.yml b/tests/integration/targets/k8s_apply/tasks/main.yml index 3d04f097f2..c759d7bcf0 100644 --- a/tests/integration/targets/k8s_apply/tasks/main.yml +++ b/tests/integration/targets/k8s_apply/tasks/main.yml @@ -717,9 +717,67 @@ - result.result.metadata.managedFields[0].manager == 'manager-02' - result.result.data.key == 'value-1' + # check_mode with server side apply + - name: Ensure namespace does not exist + k8s: + state: absent + kind: Namespace + name: testing + wait: true + + - name: Create namespace using server_side_apply=true and check_mode=true + k8s: + apply: true + server_side_apply: + field_manager: ansible + definition: + kind: Namespace + apiVersion: v1 + metadata: + name: testing + check_mode: true + register: _create + + - name: Ensure namespace was not created + k8s_info: + kind: Namespace + name: testing + register: _info + + - name: Validate that check_mode reported change even if namespace was not created + assert: + that: + - _create is changed + - not _info.resources + + # server side apply over kubernetes client releases + - name: Create temporary directory + tempfile: + state: directory + suffix: .server + register: path + + - set_fact: + virtualenv_src: "{{ path.path }}" + + - include_tasks: tasks/server_side_apply.yml + with_items: + - '24.2.0' + - '25.2.0a1' + - '25.3.0b1' + - '25.3.0' + - '23.6.0' + always: - name: Remove namespace k8s: kind: Namespace name: "{{ test_namespace }}" state: absent + + - name: Delete temporary directory + file: + path: "{{ virtualenv_src }}" + state: absent + ignore_errors: true + when: virtualenv_src is defined diff --git a/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml b/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml new file mode 100644 index 0000000000..d50bf14790 --- /dev/null +++ b/tests/integration/targets/k8s_apply/tasks/server_side_apply.yml @@ -0,0 +1,24 @@ +- set_fact: + kubernetes_version: "kubernetes=={{ item }}" + virtualenv_path: "{{ virtualenv_src }}/venv{{ item | replace('.', '') }}" + +- name: Install kubernetes version + pip: + name: + - '{{ kubernetes_version }}' + virtualenv_command: "virtualenv --python {{ ansible_python_interpreter }}" + virtualenv: "{{ virtualenv_path }}" + +- name: Update namespace using server side apply + k8s: + apply: true + server_side_apply: + field_manager: "ansible{{ item | replace('.', '') }}" + force_conflicts: true + definition: + kind: Namespace + apiVersion: v1 + metadata: + name: testing + vars: + ansible_python_interpreter: "{{ virtualenv_path }}/bin/python" diff --git a/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py b/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py index 750d8de5b9..9c9e879660 100644 --- a/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py +++ b/tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentials.py @@ -70,16 +70,10 @@ import os import shutil -from ansible.module_utils.basic import AnsibleModule, missing_required_lib - -try: - from kubernetes import client, config - from kubernetes.dynamic import DynamicClient, LazyDiscoverer - - HAS_KUBERNETES_MODULE = True - -except ImportError: - HAS_KUBERNETES_MODULE = False +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) class K8SInventoryTestModule(AnsibleModule): @@ -91,10 +85,6 @@ def __init__(self): ) super(K8SInventoryTestModule, self).__init__(argument_spec=argument_spec) - - if not HAS_KUBERNETES_MODULE: - self.fail_json(msg=missing_required_lib("kubernetes")) - self.execute_module() def execute_module(self): @@ -114,19 +104,15 @@ def execute_module(self): ) ) - client_config = type.__call__(client.Configuration) - config.load_kube_config( - config_file=kubeconfig_path, client_configuration=client_config - ) - DynamicClient(client.ApiClient(client_config), discoverer=LazyDiscoverer) + client = get_api_client(kubeconfig=kubeconfig_path) result = dict(host=os.path.join(dest_dir, "host_data.txt")) # create file containing host information with open(result["host"], "w") as fd: - fd.write(client_config.host) + fd.write(client.configuration.host) for key in ("cert_file", "key_file", "ssl_ca_cert"): dest_file = os.path.join(dest_dir, "{0}_data.txt".format(key)) - shutil.copyfile(getattr(client_config, key), dest_file) + shutil.copyfile(getattr(client.configuration, key), dest_file) result[key] = dest_file self.exit_json(auth=result)