From 0a28aa02e8548939cabce91cfed4693ab36f015d Mon Sep 17 00:00:00 2001 From: abikouo Date: Thu, 20 May 2021 10:29:30 +0200 Subject: [PATCH] updates --- changelogs/fragments/105-wait_property.yaml | 3 + molecule/default/tasks/waiter.yml | 87 +++++++++++++++++++++ plugins/doc_fragments/k8s_wait_options.py | 2 + plugins/module_utils/common.py | 10 +-- plugins/module_utils/jsonpath.py | 26 ++++-- tests/unit/module_utils/test_jsonpath.py | 5 +- 6 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 changelogs/fragments/105-wait_property.yaml diff --git a/changelogs/fragments/105-wait_property.yaml b/changelogs/fragments/105-wait_property.yaml new file mode 100644 index 00000000..4ecabd1a --- /dev/null +++ b/changelogs/fragments/105-wait_property.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - k8s - add new option ``wait_property`` to support ability to wait on arbitrary property (https://github.com/ansible-collections/kubernetes.core/pull/105). diff --git a/molecule/default/tasks/waiter.yml b/molecule/default/tasks/waiter.yml index 44fc42b3..e2c1da0c 100644 --- a/molecule/default/tasks/waiter.yml +++ b/molecule/default/tasks/waiter.yml @@ -364,6 +364,93 @@ that: - short_wait_remove_pod is failed + - name: add a simple crashing pod and wait until container is running + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + name: pod-crash-0 + namespace: "{{ wait_namespace }}" + spec: + containers: + - name: crashing-container + image: busybox + command: ['/dummy/dummy-shell', '-c', 'sleep 2000'] + wait: yes + wait_timeout: 10 + wait_property: + property: status.containerStatuses[*].state.running + ignore_errors: true + register: crash_pod + + - name: assert that task failed + assert: + that: + - crash_pod is failed + - crash_pod.changed + - '"Resource creation timed out" in crash_pod.msg' + + - name: add a valid pod and wait until container is running + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + name: pod-valid-0 + namespace: "{{ wait_namespace }}" + spec: + containers: + - name: crashing-container + image: busybox + command: ['/bin/sh', '-c', 'sleep 10000'] + wait: yes + wait_timeout: 10 + wait_property: + property: status.containerStatuses[*].state.running + ignore_errors: true + register: valid_pod + + - name: assert that task failed + assert: + that: + - valid_pod is successful + - valid_pod.changed + - valid_pod.result.status.containerStatuses[0].state.running is defined + + - name: create pod (waiting for container.ready set to false) + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + name: redis-pod + namespace: "{{ wait_namespace }}" + spec: + containers: + - name: redis-container + image: redis + volumeMounts: + - name: test + mountPath: "/etc/test" + readOnly: true + volumes: + - name: test + configMap: + name: redis-config + wait: yes + wait_timeout: 10 + wait_property: + property: status.containerStatuses[0].ready + value: "false" + register: wait_boolean + + - name: assert that pod was created but not running + assert: + that: + - wait_boolean.changed + - wait_boolean.result.status.phase == 'Pending' + always: - name: Remove namespace k8s: diff --git a/plugins/doc_fragments/k8s_wait_options.py b/plugins/doc_fragments/k8s_wait_options.py index 6b1f4d53..6303bc63 100644 --- a/plugins/doc_fragments/k8s_wait_options.py +++ b/plugins/doc_fragments/k8s_wait_options.py @@ -69,6 +69,7 @@ options: - Specifies a property on the resource to wait for. - Ignored if C(wait) is not set or is set to I(False). type: dict + version_added: '2.0.0' suboptions: property: type: str @@ -80,5 +81,6 @@ options: type: str description: - The expected value of the C(property). + - The value is not case-sensitive. - If this is missing, we will check only that the attribute C(property) is present. ''' diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index c868ee35..db5ddc62 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -434,16 +434,8 @@ class K8sAnsibleMixin(object): def _resource_absent(resource): return not resource - with open("/tmp/resource.txt", "w+") as f: - import json - f.write("------- Property -------\n{}".format(json.dumps(property, indent=2))) - def _wait_for_property(resource): - test = match_json_property(self, resource.to_dict(), property.get('property'), property.get('value', None)) - with open("/tmp/resource.txt", "w+") as f: - import json - f.write("------- test = {}\n{}".format(test, json.dumps(resource.to_dict(), indent=2))) - return test + return match_json_property(self, resource.to_dict(), property.get('property'), property.get('value', None)) waiter = dict( Deployment=_deployment_ready, diff --git a/plugins/module_utils/jsonpath.py b/plugins/module_utils/jsonpath.py index faa6e39b..411e2289 100644 --- a/plugins/module_utils/jsonpath.py +++ b/plugins/module_utils/jsonpath.py @@ -23,11 +23,10 @@ from ansible.module_utils._text import to_native try: import jmespath HAS_JMESPATH_LIB = True - jmespath_import_exception = None + JMESPATH_IMP_ERR = None except ImportError as e: HAS_JMESPATH_LIB = False - jmespath_import_exception = e - JMESPATH_IMP_ERR = traceback.format_exc() + JMESPATH_IMP_ERR = e def match_json_property(module, data, expr, value=None): @@ -44,18 +43,29 @@ def match_json_property(module, data, expr, value=None): raise err def _match_value(buf, v): - # convert all values from bool to str and lowercase them - return v.lower() in [str(i).lower() for i in buf] + if isinstance(buf, list): + # convert all values from bool to str and lowercase them + return v.lower() in [str(i).lower() for i in buf] + elif isinstance(buf, str): + return v.lower() == content.lower() + elif isinstance(buf, bool): + return v.lower() == str(content).lower() + else: + # unable to test single value against dict + return False if not HAS_JMESPATH_LIB: - _raise_or_fail(jmespath_import_exception, msg=missing_required_lib('jmespath'), exception=JMESPATH_IMP_ERR) + _raise_or_fail(JMESPATH_IMP_ERR, msg=missing_required_lib('jmespath')) jmespath.functions.REVERSE_TYPES_MAP['string'] = jmespath.functions.REVERSE_TYPES_MAP['string'] + ('AnsibleUnicode', 'AnsibleUnsafeText', ) try: content = jmespath.search(expr, data) - if not content: + with open("/tmp/play.cont", "w") as f: + f.write("{}".format(content)) + if content is None or content == []: return False - if not value or _match_value(content, value): + if value is None or _match_value(content, value): + # looking for state present return True return False except Exception as err: diff --git a/tests/unit/module_utils/test_jsonpath.py b/tests/unit/module_utils/test_jsonpath.py index 59be299a..aa7fb3f1 100644 --- a/tests/unit/module_utils/test_jsonpath.py +++ b/tests/unit/module_utils/test_jsonpath.py @@ -24,7 +24,6 @@ jmespath = pytest.importorskip("jmespath") def test_property_present(): data = { - "Kind": "Pod", "containers": [ {"name": "t0", "image": "nginx"}, {"name": "t1", "image": "python"}, @@ -37,7 +36,6 @@ def test_property_present(): def test_property_value(): data = { - "Kind": "Pod", "containers": [ {"name": "t0", "image": "nginx"}, {"name": "t1", "image": "python"}, @@ -52,7 +50,7 @@ def test_property_value(): def test_boolean_value(): data = { "containers": [ - {"image": "nginx"}, + {"image": "nginx", "poweron": False}, {"image": "python"}, {"image": "mongo", "connected": True} ] @@ -60,6 +58,7 @@ def test_boolean_value(): assert match_json_property(None, data, "containers[*].connected", "true") assert match_json_property(None, data, "containers[*].connected", "True") assert match_json_property(None, data, "containers[*].connected", "TRUE") + assert match_json_property(None, data, "containers[0].poweron", "false") def test_valid_expression():