Skip to content

Commit 9e9962b

Browse files
authored
Provide a mechanism to hide fields from output (#629)
Provide a mechanism to hide fields from output SUMMARY The k8s and k8s_info modules can be a little noisy in verbose mode, and most of that is due to managedFields. If we can provide a mechanism to hide managedFields, the output is a lot more useful. ISSUE TYPE Feature Pull Request COMPONENT NAME k8s, k8s_info ADDITIONAL INFORMATION Before ANSIBLE_COLLECTIONS_PATH=../../.. ansible -m k8s_info -a 'kind=ConfigMap name=hide-fields-cm namespace=hide-fields' localhost [WARNING]: No inventory was parsed, only implicit localhost is available localhost | SUCCESS => { "api_found": true, "changed": false, "resources": [ { "apiVersion": "v1", "data": { "another": "value", "hello": "world" }, "kind": "ConfigMap", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"another\":\"value\",\"hello\":\"world\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"hide-fields-cm\",\"namespace\":\"hide-fields\"}}\n" }, "creationTimestamp": "2023-06-13T01:47:47Z", "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:data": { ".": {}, "f:another": {}, "f:hello": {} }, "f:metadata": { "f:annotations": { ".": {}, "f:kubectl.kubernetes.io/last-applied-configuration": {} } } }, "manager": "kubectl-client-side-apply", "operation": "Update", "time": "2023-06-13T01:47:47Z" } ], "name": "hide-fields-cm", "namespace": "hide-fields", "resourceVersion": "2557394", "uid": "f233da63-6374-4079-9825-3562c0ed123c" } } ] } After ANSIBLE_COLLECTIONS_PATH=../../.. ansible -m k8s_info -a 'kind=ConfigMap name=hide-fields-cm namespace=hide-fields hidden_fields=metadata.managedFields' localhost [WARNING]: No inventory was parsed, only implicit localhost is available localhost | SUCCESS => { "api_found": true, "changed": false, "resources": [ { "apiVersion": "v1", "data": { "another": "value", "hello": "world" }, "kind": "ConfigMap", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"another\":\"value\",\"hello\":\"world\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"hide-fields-cm\",\"namespace\":\"hide-fields\"}}\n" }, "creationTimestamp": "2023-06-13T01:47:47Z", "name": "hide-fields-cm", "namespace": "hide-fields", "resourceVersion": "2557394", "uid": "f233da63-6374-4079-9825-3562c0ed123c" } } ] } Reviewed-by: Mike Graves <[email protected]> Reviewed-by: Will Thames
1 parent 9ca13c3 commit 9e9962b

File tree

11 files changed

+195
-5
lines changed

11 files changed

+195
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minor_changes:
2+
- k8s, k8s_info - add a hidden_fields option to allow fields to be hidden in the results of k8s and k8s_info

plugins/module_utils/k8s/runner.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import (
1818
K8sService,
1919
diff_objects,
20+
hide_fields,
2021
)
2122
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
2223
ResourceTimeout,
@@ -137,6 +138,7 @@ def perform_action(svc, definition: Dict, params: Dict) -> Dict:
137138
state = params.get("state", None)
138139
kind = definition.get("kind")
139140
api_version = definition.get("apiVersion")
141+
hidden_fields = params.get("hidden_fields")
140142

141143
result = {"changed": False, "result": {}}
142144
instance = {}
@@ -212,7 +214,7 @@ def perform_action(svc, definition: Dict, params: Dict) -> Dict:
212214
existing = existing.to_dict()
213215
else:
214216
existing = {}
215-
match, diffs = diff_objects(existing, instance)
217+
match, diffs = diff_objects(existing, instance, hidden_fields)
216218
if match and diffs:
217219
result.setdefault("warnings", []).append(
218220
"No meaningful diff was generated, but the API may not be idempotent "
@@ -222,7 +224,7 @@ def perform_action(svc, definition: Dict, params: Dict) -> Dict:
222224
if svc.module._diff:
223225
result["diff"] = diffs
224226

225-
result["result"] = instance
227+
result["result"] = hide_fields(instance, hidden_fields)
226228
if not success:
227229
raise ResourceTimeout(
228230
'"{0}" "{1}": Timed out waiting on resource'.format(

plugins/module_utils/k8s/service.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright: (c) 2021, Red Hat | Ansible
22
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
33

4+
import copy
45
from typing import Any, Dict, List, Optional, Tuple
56

67
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
@@ -248,6 +249,7 @@ def find(
248249
wait_timeout: Optional[int] = 120,
249250
state: Optional[str] = "present",
250251
condition: Optional[Dict] = None,
252+
hidden_fields: Optional[List] = None,
251253
) -> Dict:
252254
resource = self.find_resource(kind, api_version)
253255
api_found = bool(resource)
@@ -310,7 +312,9 @@ def find(
310312
instances = resources.get("items") or [resources]
311313

312314
if not wait:
313-
result["resources"] = instances
315+
result["resources"] = [
316+
hide_fields(instance, hidden_fields) for instance in instances
317+
]
314318
return result
315319

316320
# Now wait for the specified state of any resource instances we have found.
@@ -329,7 +333,7 @@ def find(
329333
"Failed to gather information about %s(s) even"
330334
" after waiting for %s seconds" % (res.get("kind"), duration)
331335
)
332-
result["resources"].append(res)
336+
result["resources"].append(hide_fields(res, hidden_fields))
333337
return result
334338

335339
def create(self, resource: Resource, definition: Dict) -> Dict:
@@ -495,7 +499,9 @@ def delete(
495499
return k8s_obj
496500

497501

498-
def diff_objects(existing: Dict, new: Dict) -> Tuple[bool, Dict]:
502+
def diff_objects(
503+
existing: Dict, new: Dict, hidden_fields: Optional[list] = None
504+
) -> Tuple[bool, Dict]:
499505
result = {}
500506
diff = recursive_diff(existing, new)
501507
if not diff:
@@ -517,4 +523,29 @@ def diff_objects(existing: Dict, new: Dict) -> Tuple[bool, Dict]:
517523
if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
518524
return False, result
519525

526+
result["before"] = hide_fields(result["before"], hidden_fields)
527+
result["after"] = hide_fields(result["after"], hidden_fields)
528+
520529
return True, result
530+
531+
532+
def hide_fields(definition: dict, hidden_fields: Optional[list]) -> dict:
533+
if not hidden_fields:
534+
return definition
535+
result = copy.deepcopy(definition)
536+
for hidden_field in hidden_fields:
537+
result = hide_field(result, hidden_field)
538+
return result
539+
540+
541+
# hide_field is not hugely sophisticated and designed to cope
542+
# with e.g. status or metadata.managedFields rather than e.g.
543+
# spec.template.spec.containers[0].env[3].value
544+
def hide_field(definition: dict, hidden_field: str) -> dict:
545+
split = hidden_field.split(".", 1)
546+
if split[0] in definition:
547+
if len(split) == 2:
548+
definition[split[0]] = hide_field(definition[split[0]], split[1])
549+
else:
550+
del definition[split[0]]
551+
return definition

plugins/modules/k8s.py

+9
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@
185185
version_added: 2.5.0
186186
aliases:
187187
- all
188+
hidden_fields:
189+
description:
190+
- Hide fields matching this option in the result
191+
- An example might be C(hidden_fields=[metadata.managedFields])
192+
- Only field definitions that don't reference list items are supported (so V(spec.containers[0]) would not work)
193+
type: list
194+
elements: str
195+
version_added: 2.5.0
188196
189197
requirements:
190198
- "python >= 3.6"
@@ -472,6 +480,7 @@ def argspec():
472480
type="dict", default=None, options=server_apply_spec()
473481
)
474482
argument_spec["delete_all"] = dict(type="bool", default=False, aliases=["all"])
483+
argument_spec["hidden_fields"] = dict(type="list", elements="str")
475484

476485
return argument_spec
477486

plugins/modules/k8s_info.py

+10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444
type: list
4545
elements: str
4646
default: []
47+
hidden_fields:
48+
description:
49+
- Hide fields matching any of the field definitions in the result
50+
- An example might be C(hidden_fields=[metadata.managedFields])
51+
- Only field definitions that don't reference list items are supported (so V(spec.containers[0]) would not work)
52+
type: list
53+
elements: str
54+
version_added: 2.5.0
4755
4856
extends_documentation_fragment:
4957
- kubernetes.core.k8s_auth_options
@@ -183,6 +191,7 @@ def execute_module(module, svc):
183191
wait_sleep=module.params["wait_sleep"],
184192
wait_timeout=module.params["wait_timeout"],
185193
condition=module.params["wait_condition"],
194+
hidden_fields=module.params["hidden_fields"],
186195
)
187196
module.exit_json(changed=False, **facts)
188197

@@ -198,6 +207,7 @@ def argspec():
198207
namespace=dict(),
199208
label_selectors=dict(type="list", elements="str", default=[]),
200209
field_selectors=dict(type="list", elements="str", default=[]),
210+
hidden_fields=dict(type="list", elements="str"),
201211
)
202212
)
203213
return args
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
time=59
2+
k8s
3+
k8s_info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
test_namespace: "hide-fields"
3+
hide_fields_namespace: "hide-fields"
4+
hide_fields_base_configmap:
5+
apiVersion: v1
6+
kind: ConfigMap
7+
metadata:
8+
name: hide-fields-cm
9+
namespace: hide-fields
10+
data:
11+
hello: world
12+
another: value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dependencies:
2+
- setup_namespace
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
- connection: local
3+
gather_facts: false
4+
hosts: localhost
5+
roles:
6+
- k8s_hide_fields
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -eux
3+
export ANSIBLE_CALLBACKS_ENABLED=profile_tasks
4+
export ANSIBLE_ROLES_PATH=../
5+
ansible-playbook playbook.yaml "$@"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
- block:
2+
- name: Creation with hidden fields should work
3+
k8s:
4+
definition: "{{ hide_fields_base_configmap}}"
5+
hidden_fields:
6+
- metadata.managedFields
7+
register: hf1
8+
9+
- name: Ensure hidden fields are not present
10+
assert:
11+
that:
12+
- "'managedFields' not in hf1.result['metadata']"
13+
14+
- name: Running without hidden fields should work
15+
k8s:
16+
definition: "{{ hide_fields_base_configmap}}"
17+
18+
- name: Running with missing hidden fields should have no effect
19+
k8s:
20+
definition: "{{ hide_fields_base_configmap}}"
21+
hidden_fields:
22+
- does.not.exist
23+
register: hf2
24+
25+
- name: Ensure no change with missing hidden fields
26+
assert:
27+
that:
28+
- not hf2.changed
29+
30+
- name: Hide status and managed fields
31+
k8s:
32+
definition: "{{ hide_fields_base_configmap}}"
33+
hidden_fields:
34+
- status
35+
- metadata.managedFields
36+
register: hf3
37+
diff: true
38+
39+
- name: Ensure hidden fields are not present
40+
assert:
41+
that:
42+
- "'status' not in hf3.result"
43+
- "'managedFields' not in hf3.result['metadata']"
44+
45+
- name: k8s_info works with hidden fields
46+
k8s_info:
47+
name: "{{ hide_fields_base_configmap.metadata.name }}"
48+
namespace: "{{ hide_fields_base_configmap.metadata.namespace }}"
49+
kind: ConfigMap
50+
hidden_fields:
51+
- metadata.managedFields
52+
register: hf4
53+
54+
- name: Ensure hidden fields are not present
55+
assert:
56+
that:
57+
- hf4.resources | length == 1
58+
- "'managedFields' not in hf4.resources[0]['metadata']"
59+
60+
61+
- name: Hiding a changed field should still result in a change
62+
k8s:
63+
definition: "{{ hide_fields_base_configmap | combine({'data':{'hello':'different'}}) }}"
64+
hidden_fields:
65+
- data
66+
- metadata.managedFields
67+
register: hf5
68+
diff: true
69+
70+
- name: Ensure that hidden changed field changed
71+
assert:
72+
that:
73+
- hf5.changed
74+
75+
- name: Apply works with hidden fields
76+
k8s:
77+
definition: "{{ hide_fields_base_configmap | combine({'data':{'anew':'value'}}) }}"
78+
hidden_fields:
79+
- data
80+
apply: true
81+
register: hf6
82+
diff: true
83+
84+
- name: Ensure that hidden changed field changed
85+
assert:
86+
that:
87+
- hf6.changed
88+
89+
- name: Hidden field should not show up in deletion
90+
k8s:
91+
definition: "{{ hide_fields_base_configmap}}"
92+
hidden_fields:
93+
- status
94+
state: absent
95+
register: hf7
96+
97+
- name: Ensure hidden fields are not present
98+
assert:
99+
that:
100+
- "'status' not in hf7.result"
101+
102+
always:
103+
- name: Remove namespace
104+
k8s:
105+
kind: Namespace
106+
name: "{{ hide_fields_namespace }}"
107+
state: absent
108+
ignore_errors: true

0 commit comments

Comments
 (0)