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 <mgraves@redhat.com>
Reviewed-by: Bikouo Aubin <None>
This commit is contained in:
Bikouo Aubin
2022-12-15 19:17:17 +01:00
committed by GitHub
parent 979b492233
commit c073eea5b3
5 changed files with 105 additions and 22 deletions

View File

@@ -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).

View File

@@ -24,6 +24,13 @@ from ansible.module_utils.common.dict_transformations import dict_merge
from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import ( from ansible_collections.kubernetes.core.plugins.module_utils.exceptions import (
ApplyException, 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: try:
from kubernetes.dynamic.exceptions import NotFoundError from kubernetes.dynamic.exceptions import NotFoundError
@@ -131,7 +138,10 @@ def k8s_apply(resource, definition, **kwargs):
existing, desired = apply_object(resource, definition) existing, desired = apply_object(resource, definition)
server_side = kwargs.get("server_side", False) server_side = kwargs.get("server_side", False)
if server_side: 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' # server_side_apply is forces content_type to 'application/apply-patch+yaml'
return resource.server_side_apply( return resource.server_side_apply(
body=body, body=body,
@@ -139,6 +149,7 @@ def k8s_apply(resource, definition, **kwargs):
namespace=definition["metadata"].get("namespace"), namespace=definition["metadata"].get("namespace"),
force_conflicts=kwargs.get("force_conflicts"), force_conflicts=kwargs.get("force_conflicts"),
field_manager=kwargs.get("field_manager"), field_manager=kwargs.get("field_manager"),
dry_run=kwargs.get("dry_run"),
) )
if not existing: if not existing:
return resource.create( return resource.create(

View File

@@ -717,9 +717,67 @@
- result.result.metadata.managedFields[0].manager == 'manager-02' - result.result.metadata.managedFields[0].manager == 'manager-02'
- result.result.data.key == 'value-1' - 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: always:
- name: Remove namespace - name: Remove namespace
k8s: k8s:
kind: Namespace kind: Namespace
name: "{{ test_namespace }}" name: "{{ test_namespace }}"
state: absent state: absent
- name: Delete temporary directory
file:
path: "{{ virtualenv_src }}"
state: absent
ignore_errors: true
when: virtualenv_src is defined

View File

@@ -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"

View File

@@ -70,16 +70,10 @@ auth:
import os import os
import shutil import shutil
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
try: get_api_client,
from kubernetes import client, config )
from kubernetes.dynamic import DynamicClient, LazyDiscoverer
HAS_KUBERNETES_MODULE = True
except ImportError:
HAS_KUBERNETES_MODULE = False
class K8SInventoryTestModule(AnsibleModule): class K8SInventoryTestModule(AnsibleModule):
@@ -91,10 +85,6 @@ class K8SInventoryTestModule(AnsibleModule):
) )
super(K8SInventoryTestModule, self).__init__(argument_spec=argument_spec) super(K8SInventoryTestModule, self).__init__(argument_spec=argument_spec)
if not HAS_KUBERNETES_MODULE:
self.fail_json(msg=missing_required_lib("kubernetes"))
self.execute_module() self.execute_module()
def execute_module(self): def execute_module(self):
@@ -114,19 +104,15 @@ class K8SInventoryTestModule(AnsibleModule):
) )
) )
client_config = type.__call__(client.Configuration) client = get_api_client(kubeconfig=kubeconfig_path)
config.load_kube_config(
config_file=kubeconfig_path, client_configuration=client_config
)
DynamicClient(client.ApiClient(client_config), discoverer=LazyDiscoverer)
result = dict(host=os.path.join(dest_dir, "host_data.txt")) result = dict(host=os.path.join(dest_dir, "host_data.txt"))
# create file containing host information # create file containing host information
with open(result["host"], "w") as fd: 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"): for key in ("cert_file", "key_file", "ssl_ca_cert"):
dest_file = os.path.join(dest_dir, "{0}_data.txt".format(key)) 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 result[key] = dest_file
self.exit_json(auth=result) self.exit_json(auth=result)