diff --git a/changelogs/fragments/571-k8s_info-fix-issue-with-api-server.yaml b/changelogs/fragments/571-k8s_info-fix-issue-with-api-server.yaml new file mode 100644 index 00000000000..b8263037de5 --- /dev/null +++ b/changelogs/fragments/571-k8s_info-fix-issue-with-api-server.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - k8s_info - fix issue when module returns successful true after the resource cache has been established during periods where communication to the api-server is not possible (https://github.com/ansible-collections/kubernetes.core/issues/508). diff --git a/plugins/module_utils/k8s/service.py b/plugins/module_utils/k8s/service.py index 54307da1ed9..6a32f9a8410 100644 --- a/plugins/module_utils/k8s/service.py +++ b/plugins/module_utils/k8s/service.py @@ -23,7 +23,6 @@ ) from ansible.module_utils.common.dict_transformations import dict_merge -from ansible.module_utils._text import to_native try: from kubernetes.dynamic.exceptions import ( @@ -268,8 +267,13 @@ def find( except BadRequestError: return result except CoreException as e: - result["msg"] = to_native(e) - return result + raise e + except Exception as e: + raise CoreException( + "Exception '{0}' raised while trying to get resource using (name={1}, namespace={2}, label_selectors={3}, field_selectors={4})".format( + e, name, namespace, label_selectors, field_selectors + ) + ) # There is either no result or there is a List resource with no items if ( diff --git a/tests/integration/targets/k8s_info/defaults/main.yml b/tests/integration/targets/k8s_info/defaults/main.yml index 26b87c7f4ae..66ecab296a6 100644 --- a/tests/integration/targets/k8s_info/defaults/main.yml +++ b/tests/integration/targets/k8s_info/defaults/main.yml @@ -1,3 +1,5 @@ --- -test_namespace: "wait" +test_namespace: + - wait + - python-api-caching k8s_wait_timeout: 400 diff --git a/tests/integration/targets/k8s_info/tasks/api-server-caching.yml b/tests/integration/targets/k8s_info/tasks/api-server-caching.yml new file mode 100644 index 00000000000..43e835fd241 --- /dev/null +++ b/tests/integration/targets/k8s_info/tasks/api-server-caching.yml @@ -0,0 +1,91 @@ +--- +- name: create temporary directory for tests + tempfile: + state: directory + suffix: .test + register: _testdir + +- block: + - name: Create kubernetes secret + k8s: + namespace: '{{ api_namespace }}' + definition: + apiVersion: v1 + kind: Secret + metadata: + name: '{{ test_secret }}' + type: Opaque + stringData: + foo: bar + + - name: save default kubeconfig + copy: + src: ~/.kube/config + dest: '{{ _testdir.path }}/config' + + - name: create bad kubeconfig + copy: + src: '{{ _testdir.path }}/config' + dest: '{{ _testdir.path }}/badconfig' + + - name: Remove certificate-data from badconfig + ansible.builtin.lineinfile: + path: '{{ _testdir.path }}/badconfig' + regexp: "(.*)certificate-authority-data(.*)" + state: absent + + - name: Delete default config + file: + state: absent + path: ~/.kube/config + + - name: Check for existing cluster namespace with good kubeconfig + k8s_info: + api_version: v1 + kind: Secret + name: '{{ test_secret }}' + namespace: '{{ api_namespace }}' + kubeconfig: '{{ _testdir.path }}/config' + register: result_good_config + + - name: Check for existing cluster namespace with bad kubeconfig + k8s_info: + api_version: v1 + kind: Secret + name: '{{ test_secret }}' + namespace: '{{ api_namespace }}' + kubeconfig: '{{ _testdir.path }}/badconfig' + register: result_bad_config + ignore_errors: true + + - name: Ensure task has failed with proper message + assert: + that: + - result_good_config is successful + - result_good_config.resources | length == 1 + - result_bad_config is failed + - '"certificate verify failed" in result_bad_config.msg' + + vars: + api_namespace: "{{ test_namespace[1] }}" + test_secret: "my-secret" + + always: + + - name: restore default kubeconfig + copy: + dest: ~/.kube/config + src: '{{ _testdir.path }}/config' + + - name: Delete namespace + k8s: + kind: namespace + name: "{{ api_namespace }}" + state: absent + ignore_errors: true + + - name: delete temporary directory + file: + state: absent + path: '{{ _testdir.path }}' + ignore_errors: true diff --git a/tests/integration/targets/k8s_info/tasks/main.yml b/tests/integration/targets/k8s_info/tasks/main.yml index 7a8efeec058..f15274a58ad 100644 --- a/tests/integration/targets/k8s_info/tasks/main.yml +++ b/tests/integration/targets/k8s_info/tasks/main.yml @@ -1,238 +1,5 @@ --- -- block: - - set_fact: - wait_namespace: "{{ test_namespace }}" - multi_pod_one: multi-pod-1 - multi_pod_two: multi-pod-2 - - - name: Add a simple pod with initContainer - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - name: "{{ k8s_pod_name }}" - namespace: "{{ wait_namespace }}" - spec: - initContainers: - - name: init-01 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 20'] - containers: - - name: utilitypod-01 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 360'] - - - name: Wait and gather information about new pod - k8s_info: - name: "{{ k8s_pod_name }}" - kind: Pod - namespace: "{{ wait_namespace }}" - wait: yes - wait_sleep: 5 - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - register: wait_info - - - name: Assert that pod creation succeeded - assert: - that: - - wait_info is successful - - not wait_info.changed - - wait_info.resources[0].status.phase == "Running" - - - name: Remove Pod - k8s: - api_version: v1 - kind: Pod - name: "{{ k8s_pod_name }}" - namespace: "{{ wait_namespace }}" - state: absent - wait: yes - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - ignore_errors: yes - register: short_wait_remove_pod - - - name: Check if pod is removed - assert: - that: - - short_wait_remove_pod is successful - - short_wait_remove_pod.changed - - - name: Create multiple pod with initContainer - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - labels: - run: multi-box - name: "{{ multi_pod_one }}" - namespace: "{{ wait_namespace }}" - spec: - initContainers: - - name: init-01 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 25'] - containers: - - name: multi-pod-01 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 360'] - - - name: Create another pod with same label as previous pod - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - labels: - run: multi-box - name: "{{ multi_pod_two }}" - namespace: "{{ wait_namespace }}" - spec: - initContainers: - - name: init-02 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 25'] - containers: - - name: multi-pod-02 - image: python:3.7-alpine - command: ['sh', '-c', 'sleep 360'] - - - name: Wait and gather information about new pods - k8s_info: - kind: Pod - namespace: "{{ wait_namespace }}" - wait: yes - wait_sleep: 5 - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - label_selectors: - - run == multi-box - register: wait_info - - - name: Assert that pod creation succeeded - assert: - that: - - wait_info is successful - - not wait_info.changed - - wait_info.resources[0].status.phase == "Running" - - wait_info.resources[1].status.phase == "Running" - - - name: "Remove Pod {{ multi_pod_one }}" - k8s: - api_version: v1 - kind: Pod - name: "{{ multi_pod_one }}" - namespace: "{{ wait_namespace }}" - state: absent - wait: yes - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - ignore_errors: yes - register: multi_pod_one_remove - - - name: "Check if {{ multi_pod_one }} pod is removed" - assert: - that: - - multi_pod_one_remove is successful - - multi_pod_one_remove.changed - - - name: "Remove Pod {{ multi_pod_two }}" - k8s: - api_version: v1 - kind: Pod - name: "{{ multi_pod_two }}" - namespace: "{{ wait_namespace }}" - state: absent - wait: yes - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - ignore_errors: yes - register: multi_pod_two_remove - - - name: "Check if {{ multi_pod_two }} pod is removed" - assert: - that: - - multi_pod_two_remove is successful - - multi_pod_two_remove.changed - - - name: "Look for existing API" - k8s_info: - api_version: apps/v1 - kind: Deployment - register: existing_api - - - name: Check if we informed the user the api does exist - assert: - that: - - existing_api.api_found - - - name: "Look for non-existent API" - k8s_info: - api_version: pleasedonotcreatethisresource.example.com/v7 - kind: DoesNotExist - register: dne_api - - - name: Check if we informed the user the api does not exist - assert: - that: - - not dne_api.resources - - not dne_api.api_found - - - name: Start timer - set_fact: - start: "{{ lookup('pipe', 'date +%s') }}" - - - name: Wait for non-existent pod to be created - k8s_info: - kind: Pod - name: does-not-exist - namespace: "{{ wait_namespace }}" - wait: yes - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - register: result - - - name: Check that module waited - assert: - that: - - "{{ lookup('pipe', 'date +%s') }} - {{ start }} > 30" - - - name: Create simple pod - k8s: - definition: - apiVersion: v1 - kind: Pod - metadata: - name: wait-pod-1 - namespace: "{{ wait_namespace }}" - spec: - containers: - - image: busybox - name: busybox - command: - - /bin/sh - - -c - - while true; do sleep 5; done - - - name: Wait for multiple non-existent pods to be created - k8s_info: - kind: Pod - namespace: "{{ wait_namespace }}" - label_selectors: - - thislabel=doesnotexist - wait: yes - wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" - register: result - - - name: Assert no pods were found - assert: - that: - - not result.resources - - vars: - k8s_pod_name: pod-info-1 - - always: - - name: Remove namespace - k8s: - kind: Namespace - name: "{{ wait_namespace }}" - state: absent - ignore_errors: true +- include_tasks: "tasks/{{ item }}.yml" + with_items: + - wait + - api-server-caching diff --git a/tests/integration/targets/k8s_info/tasks/wait.yml b/tests/integration/targets/k8s_info/tasks/wait.yml new file mode 100644 index 00000000000..2608f82089d --- /dev/null +++ b/tests/integration/targets/k8s_info/tasks/wait.yml @@ -0,0 +1,238 @@ +--- +- block: + - set_fact: + wait_namespace: "{{ test_namespace[0] }}" + multi_pod_one: multi-pod-1 + multi_pod_two: multi-pod-2 + + - name: Add a simple pod with initContainer + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + name: "{{ k8s_pod_name }}" + namespace: "{{ wait_namespace }}" + spec: + initContainers: + - name: init-01 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 20'] + containers: + - name: utilitypod-01 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 360'] + + - name: Wait and gather information about new pod + k8s_info: + name: "{{ k8s_pod_name }}" + kind: Pod + namespace: "{{ wait_namespace }}" + wait: yes + wait_sleep: 5 + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + register: wait_info + + - name: Assert that pod creation succeeded + assert: + that: + - wait_info is successful + - not wait_info.changed + - wait_info.resources[0].status.phase == "Running" + + - name: Remove Pod + k8s: + api_version: v1 + kind: Pod + name: "{{ k8s_pod_name }}" + namespace: "{{ wait_namespace }}" + state: absent + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + ignore_errors: yes + register: short_wait_remove_pod + + - name: Check if pod is removed + assert: + that: + - short_wait_remove_pod is successful + - short_wait_remove_pod.changed + + - name: Create multiple pod with initContainer + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + labels: + run: multi-box + name: "{{ multi_pod_one }}" + namespace: "{{ wait_namespace }}" + spec: + initContainers: + - name: init-01 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 25'] + containers: + - name: multi-pod-01 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 360'] + + - name: Create another pod with same label as previous pod + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + labels: + run: multi-box + name: "{{ multi_pod_two }}" + namespace: "{{ wait_namespace }}" + spec: + initContainers: + - name: init-02 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 25'] + containers: + - name: multi-pod-02 + image: python:3.7-alpine + command: ['sh', '-c', 'sleep 360'] + + - name: Wait and gather information about new pods + k8s_info: + kind: Pod + namespace: "{{ wait_namespace }}" + wait: yes + wait_sleep: 5 + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + label_selectors: + - run == multi-box + register: wait_info + + - name: Assert that pod creation succeeded + assert: + that: + - wait_info is successful + - not wait_info.changed + - wait_info.resources[0].status.phase == "Running" + - wait_info.resources[1].status.phase == "Running" + + - name: "Remove Pod {{ multi_pod_one }}" + k8s: + api_version: v1 + kind: Pod + name: "{{ multi_pod_one }}" + namespace: "{{ wait_namespace }}" + state: absent + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + ignore_errors: yes + register: multi_pod_one_remove + + - name: "Check if {{ multi_pod_one }} pod is removed" + assert: + that: + - multi_pod_one_remove is successful + - multi_pod_one_remove.changed + + - name: "Remove Pod {{ multi_pod_two }}" + k8s: + api_version: v1 + kind: Pod + name: "{{ multi_pod_two }}" + namespace: "{{ wait_namespace }}" + state: absent + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + ignore_errors: yes + register: multi_pod_two_remove + + - name: "Check if {{ multi_pod_two }} pod is removed" + assert: + that: + - multi_pod_two_remove is successful + - multi_pod_two_remove.changed + + - name: "Look for existing API" + k8s_info: + api_version: apps/v1 + kind: Deployment + register: existing_api + + - name: Check if we informed the user the api does exist + assert: + that: + - existing_api.api_found + + - name: "Look for non-existent API" + k8s_info: + api_version: pleasedonotcreatethisresource.example.com/v7 + kind: DoesNotExist + register: dne_api + + - name: Check if we informed the user the api does not exist + assert: + that: + - not dne_api.resources + - not dne_api.api_found + + - name: Start timer + set_fact: + start: "{{ lookup('pipe', 'date +%s') }}" + + - name: Wait for non-existent pod to be created + k8s_info: + kind: Pod + name: does-not-exist + namespace: "{{ wait_namespace }}" + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + register: result + + - name: Check that module waited + assert: + that: + - "{{ lookup('pipe', 'date +%s') }} - {{ start }} > 30" + + - name: Create simple pod + k8s: + definition: + apiVersion: v1 + kind: Pod + metadata: + name: wait-pod-1 + namespace: "{{ wait_namespace }}" + spec: + containers: + - image: busybox + name: busybox + command: + - /bin/sh + - -c + - while true; do sleep 5; done + + - name: Wait for multiple non-existent pods to be created + k8s_info: + kind: Pod + namespace: "{{ wait_namespace }}" + label_selectors: + - thislabel=doesnotexist + wait: yes + wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" + register: result + + - name: Assert no pods were found + assert: + that: + - not result.resources + + vars: + k8s_pod_name: pod-info-1 + + always: + - name: Remove namespace + k8s: + kind: Namespace + name: "{{ wait_namespace }}" + state: absent + ignore_errors: true