diff --git a/changelogs/fragments/20250720-k8s-patch-add-hidden-fields.yaml b/changelogs/fragments/20250720-k8s-patch-add-hidden-fields.yaml new file mode 100644 index 00000000..aeae63b2 --- /dev/null +++ b/changelogs/fragments/20250720-k8s-patch-add-hidden-fields.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Module k8s_json_patch - Add support for `hidden_fields` (https://github.com/ansible-collections/kubernetes.core/pull/964). diff --git a/plugins/modules/k8s_json_patch.py b/plugins/modules/k8s_json_patch.py index 92b652ca..decef21c 100644 --- a/plugins/modules/k8s_json_patch.py +++ b/plugins/modules/k8s_json_patch.py @@ -33,6 +33,14 @@ options: aliases: - api - version + hidden_fields: + description: + - List of fields to hide from the diff output. + - This is useful for fields that are not relevant to the patch operation, such as `metadata.managedFields`. + type: list + elements: str + default: [] + version_added: 6.1.0 kind: description: - Use to specify an object model. @@ -147,6 +155,7 @@ from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions imp ) from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( diff_objects, + hide_fields, ) from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import ( get_waiter, @@ -174,6 +183,7 @@ JSON_PATCH_ARGS = { "namespace": {"type": "str"}, "name": {"type": "str", "required": True}, "patch": {"type": "list", "required": True, "elements": "dict"}, + "hidden_fields": {"type": "list", "elements": "str", "default": []}, } @@ -203,6 +213,7 @@ def execute_module(module, client): namespace = module.params.get("namespace") patch = module.params.get("patch") + hidden_fields = module.params.get("hidden_fields") wait = module.params.get("wait") wait_sleep = module.params.get("wait_sleep") wait_timeout = module.params.get("wait_timeout") @@ -260,13 +271,13 @@ def execute_module(module, client): module.fail_json(msg=msg, error=to_native(exc), status="", reason="") success = True - result = {"result": obj} + result = {"result": hide_fields(obj, hidden_fields)} if wait and not module.check_mode: waiter = get_waiter(client, resource, condition=wait_condition) success, result["result"], result["duration"] = waiter.wait( wait_timeout, wait_sleep, name, namespace ) - match, diffs = diff_objects(existing.to_dict(), obj) + match, diffs = diff_objects(existing.to_dict(), obj, hidden_fields) result["changed"] = not match if module._diff: result["diff"] = diffs diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/aliases b/tests/integration/targets/k8s_json_patch_hide_fields/aliases new file mode 100644 index 00000000..65745d7f --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/aliases @@ -0,0 +1,3 @@ +k8s_json_patch +k8s +time=33 \ No newline at end of file diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/defaults/main.yml b/tests/integration/targets/k8s_json_patch_hide_fields/defaults/main.yml new file mode 100644 index 00000000..8d37beb4 --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/defaults/main.yml @@ -0,0 +1,2 @@ +--- +test_namespace: "k8s-hide-fields" diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/meta/main.yml b/tests/integration/targets/k8s_json_patch_hide_fields/meta/main.yml new file mode 100644 index 00000000..08362c78 --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- setup_namespace diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/playbook.yaml b/tests/integration/targets/k8s_json_patch_hide_fields/playbook.yaml new file mode 100644 index 00000000..5541bddb --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/playbook.yaml @@ -0,0 +1,6 @@ +--- +- connection: local + gather_facts: false + hosts: localhost + roles: + - k8s_json_patch_hide_fields diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/runme.sh b/tests/integration/targets/k8s_json_patch_hide_fields/runme.sh new file mode 100755 index 00000000..29fda1c9 --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eux +export ANSIBLE_CALLBACKS_ENABLED=profile_tasks +export ANSIBLE_ROLES_PATH=../ +ansible-playbook playbook.yaml "$@" diff --git a/tests/integration/targets/k8s_json_patch_hide_fields/tasks/main.yml b/tests/integration/targets/k8s_json_patch_hide_fields/tasks/main.yml new file mode 100644 index 00000000..d0b7ebec --- /dev/null +++ b/tests/integration/targets/k8s_json_patch_hide_fields/tasks/main.yml @@ -0,0 +1,91 @@ +- vars: + pod: json-patch + k8s_wait_timeout: 400 + + block: + - name: Create a simple pod + kubernetes.core.k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + namespace: "{{ test_namespace }}" + name: "{{ pod }}" + labels: + label1: foo + spec: + containers: + - image: busybox:musl + name: busybox + command: + - sh + - -c + - while true; do echo $(date); sleep 10; done + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + + + - name: Add a label, and hide some fields + kubernetes.core.k8s_json_patch: + kind: Pod + namespace: "{{ test_namespace }}" + name: "{{ pod }}" + patch: + - op: add + path: /metadata/labels/label2 + value: bar + hidden_fields: + - metadata.managedFields + register: hf1 + + - name: Ensure hidden fields are not present + assert: + that: + - "'managedFields' not in hf1.result['metadata']" + + + - name: Add a label, without hiding our fields + kubernetes.core.k8s_json_patch: + kind: Pod + namespace: "{{ test_namespace }}" + name: "{{ pod }}" + patch: + - op: add + path: /metadata/labels/label3 + value: bar + hidden_fields: + - something.else + register: hf2 + + - name: Ensure hidden fields are present + assert: + that: + - "'managedFields' in hf2.result['metadata']" + + + - name: Patching the same resource with missing hidden fields should have no effect + kubernetes.core.k8s_json_patch: + kind: Pod + namespace: "{{ test_namespace }}" + name: "{{ pod }}" + patch: + - op: add + path: /metadata/labels/label2 + value: bar + hidden_fields: + - does.not.exist + register: hf2 + + - name: Ensure no change with missing hidden fields + assert: + that: + - not hf2.changed + + + always: + - name: Remove namespace + k8s: + kind: Namespace + name: "{{ test_namespace }}" + state: absent + ignore_errors: true