mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
Extend hidden_fields to allow more complicated field definitions (#872)
SUMMARY This allows us to ignore e.g. the last-applied-configuration annotation by specifying metadata.annotations[kubectl.kubernetes.io/last-applied-configuration] ISSUE TYPE Feature Pull Request COMPONENT NAME hidden_fields This replaces #643 as I no longer have permissions to push to branches in this repo Reviewed-by: Bikouo Aubin Reviewed-by: Helen Bailey <hebailey@redhat.com> Reviewed-by: GomathiselviS <gomathiselvi@gmail.com> Reviewed-by: Alina Buzachis
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import copy
|
||||
from json import loads
|
||||
from re import compile
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.hashes import (
|
||||
@@ -501,47 +501,107 @@ def diff_objects(
|
||||
result["before"] = diff[0]
|
||||
result["after"] = diff[1]
|
||||
|
||||
if list(result["after"].keys()) != ["metadata"] or list(
|
||||
if list(result["after"].keys()) == ["metadata"] and list(
|
||||
result["before"].keys()
|
||||
) != ["metadata"]:
|
||||
return False, result
|
||||
) == ["metadata"]:
|
||||
# If only metadata.generation and metadata.resourceVersion changed, ignore it
|
||||
ignored_keys = set(["generation", "resourceVersion"])
|
||||
|
||||
# If only metadata.generation and metadata.resourceVersion changed, ignore it
|
||||
ignored_keys = set(["generation", "resourceVersion"])
|
||||
|
||||
if not set(result["after"]["metadata"].keys()).issubset(ignored_keys):
|
||||
return False, result
|
||||
if not set(result["before"]["metadata"].keys()).issubset(ignored_keys):
|
||||
return False, result
|
||||
if set(result["after"]["metadata"].keys()).issubset(ignored_keys) and set(
|
||||
result["before"]["metadata"].keys()
|
||||
).issubset(ignored_keys):
|
||||
return True, result
|
||||
|
||||
result["before"] = hide_fields(result["before"], hidden_fields)
|
||||
result["after"] = hide_fields(result["after"], hidden_fields)
|
||||
|
||||
return True, result
|
||||
return False, result
|
||||
|
||||
|
||||
def hide_fields(definition: dict, hidden_fields: Optional[list]) -> dict:
|
||||
if not hidden_fields:
|
||||
return definition
|
||||
result = copy.deepcopy(definition)
|
||||
for hidden_field in hidden_fields:
|
||||
result = hide_field(result, hidden_field)
|
||||
def hide_field_tree(hidden_field: str) -> List[str]:
|
||||
result = []
|
||||
key, rest = hide_field_split2(hidden_field)
|
||||
result.append(key)
|
||||
while rest:
|
||||
key, rest = hide_field_split2(rest)
|
||||
result.append(key)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# hide_field is not hugely sophisticated and designed to cope
|
||||
# with e.g. status or metadata.managedFields rather than e.g.
|
||||
# spec.template.spec.containers[0].env[3].value
|
||||
def hide_field(definition: dict, hidden_field: str) -> dict:
|
||||
split = hidden_field.split(".", 1)
|
||||
if split[0] in definition:
|
||||
if len(split) == 2:
|
||||
definition[split[0]] = hide_field(definition[split[0]], split[1])
|
||||
else:
|
||||
del definition[split[0]]
|
||||
def build_hidden_field_tree(hidden_fields: List[str]) -> Dict[str, Any]:
|
||||
"""Group hidden field targeting the same json key
|
||||
Example:
|
||||
Input: ['env[3]', 'env[0]']
|
||||
Output: {'env': [0, 3]}
|
||||
"""
|
||||
output = {}
|
||||
for hidden_field in hidden_fields:
|
||||
current = output
|
||||
tree = hide_field_tree(hidden_field)
|
||||
for idx, key in enumerate(tree):
|
||||
if current.get(key, "") is None:
|
||||
break
|
||||
if idx == (len(tree) - 1):
|
||||
current[key] = None
|
||||
elif key not in current:
|
||||
current[key] = {}
|
||||
current = current[key]
|
||||
return output
|
||||
|
||||
|
||||
# hide_field should be able to cope with simple or more complicated
|
||||
# field definitions
|
||||
# e.g. status or metadata.managedFields or
|
||||
# spec.template.spec.containers[0].env[3].value or
|
||||
# metadata.annotations[kubectl.kubernetes.io/last-applied-configuration]
|
||||
def hide_field(
|
||||
definition: Union[Dict[str, Any], List[Any]], hidden_field: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
def dict_contains_key(obj: Dict[str, Any], key: str) -> bool:
|
||||
return key in obj
|
||||
|
||||
def list_contains_key(obj: List[Any], key: str) -> bool:
|
||||
return int(key) < len(obj)
|
||||
|
||||
hidden_keys = list(hidden_field.keys())
|
||||
field_contains_key = dict_contains_key
|
||||
field_get_key = str
|
||||
if isinstance(definition, list):
|
||||
# Sort with reverse=true so that when we delete an item from the list, the order is not changed
|
||||
hidden_keys = sorted(
|
||||
[k for k in hidden_field.keys() if k.isdecimal()], reverse=True
|
||||
)
|
||||
field_contains_key = list_contains_key
|
||||
field_get_key = int
|
||||
|
||||
for key in hidden_keys:
|
||||
if field_contains_key(definition, key):
|
||||
value = hidden_field.get(key)
|
||||
convert_key = field_get_key(key)
|
||||
if value is None:
|
||||
del definition[convert_key]
|
||||
else:
|
||||
definition[convert_key] = hide_field(definition[convert_key], value)
|
||||
if (
|
||||
definition[convert_key] == dict()
|
||||
or definition[convert_key] == list()
|
||||
):
|
||||
del definition[convert_key]
|
||||
|
||||
return definition
|
||||
|
||||
|
||||
def hide_fields(
|
||||
definition: Dict[str, Any], hidden_fields: Optional[List[str]]
|
||||
) -> Dict[str, Any]:
|
||||
if not hidden_fields:
|
||||
return definition
|
||||
result = copy.deepcopy(definition)
|
||||
hidden_field_tree = build_hidden_field_tree(hidden_fields)
|
||||
return hide_field(result, hidden_field_tree)
|
||||
|
||||
|
||||
def decode_response(resp) -> Tuple[Dict, List[str]]:
|
||||
"""
|
||||
This function decodes unserialized responses from the Kubernetes python
|
||||
@@ -620,3 +680,35 @@ def parse_quoted_string(quoted_string: str) -> Tuple[str, str]:
|
||||
raise ValueError("invalid quoted string: missing closing quote")
|
||||
|
||||
return "".join(result), remainder
|
||||
|
||||
|
||||
# hide_field_split2 returns the first key in hidden_field and the rest of the hidden_field
|
||||
# We expect the first key to either be in brackets, to be terminated by the start of a left
|
||||
# bracket, or to be terminated by a dot.
|
||||
|
||||
# examples would be:
|
||||
# field.another.next -> (field, another.next)
|
||||
# field[key].value -> (field, [key].value)
|
||||
# [key].value -> (key, value)
|
||||
# [one][two] -> (one, [two])
|
||||
|
||||
|
||||
def hide_field_split2(hidden_field: str) -> Tuple[str, str]:
|
||||
lbracket = hidden_field.find("[")
|
||||
rbracket = hidden_field.find("]")
|
||||
dot = hidden_field.find(".")
|
||||
|
||||
if lbracket == 0:
|
||||
# skip past right bracket and any following dot
|
||||
rest = hidden_field[rbracket + 1 :] # noqa: E203
|
||||
if rest and rest[0] == ".":
|
||||
rest = rest[1:]
|
||||
return (hidden_field[lbracket + 1 : rbracket], rest) # noqa: E203
|
||||
|
||||
if lbracket != -1 and (dot == -1 or lbracket < dot):
|
||||
return (hidden_field[:lbracket], hidden_field[lbracket:])
|
||||
|
||||
split = hidden_field.split(".", 1)
|
||||
if len(split) == 1:
|
||||
return split[0], ""
|
||||
return split
|
||||
|
||||
@@ -188,7 +188,8 @@ options:
|
||||
description:
|
||||
- Hide fields matching this option in the result
|
||||
- An example might be C(hidden_fields=[metadata.managedFields])
|
||||
- Only field definitions that don't reference list items are supported (so V(spec.containers[0]) would not work)
|
||||
or V(hidden_fields=[spec.containers[0].env[3].value])
|
||||
or V(hidden_fields=[metadata.annotations[kubectl.kubernetes.io/last-applied-configuration]])
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 3.0.0
|
||||
|
||||
@@ -48,7 +48,8 @@ options:
|
||||
description:
|
||||
- Hide fields matching any of the field definitions in the result
|
||||
- An example might be C(hidden_fields=[metadata.managedFields])
|
||||
- Only field definitions that don't reference list items are supported (so V(spec.containers[0]) would not work)
|
||||
or V(hidden_fields=[spec.containers[0].env[3].value])
|
||||
or V(hidden_fields=[metadata.annotations[kubectl.kubernetes.io/last-applied-configuration]])
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 3.0.0
|
||||
|
||||
Reference in New Issue
Block a user