diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 4323bf87..de6b7c90 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -50,6 +50,7 @@ jobs: source: "./source" cloud_common: "./cloudcommon" ansible_posix: "./ansible_posix" + community_general: "./community_general" strategy: fail-fast: false matrix: @@ -61,7 +62,7 @@ jobs: - true - false workflow-id: ${{ fromJson(needs.splitter.outputs.test_jobs) }} - name: "integration-py${{ matrix.python-version }}-${{ matrix.ansible-version }}-${{ matrix.workflow-id }}" + name: "integration-py${{ matrix.python-version }}-${{ matrix.ansible-version }}-${{ matrix.workflow-id }}-enable_turbo=${{ matrix.enable-turbo-mode }}" steps: - name: Read target id: read-targets @@ -118,6 +119,13 @@ jobs: path: ${{ env.ansible_posix }} ref: main + - name: checkout ansible-collections/community.general + uses: ansible-network/github_actions/.github/actions/checkout_dependency@main + with: + repository: ansible-collections/community.general + path: ${{ env.community_general }} + ref: main + - name: install cloud.common collection uses: ansible-network/github_actions/.github/actions/build_install_collection@main with: @@ -130,6 +138,12 @@ jobs: install_python_dependencies: true source_path: ${{ env.ansible_posix }} + - name: install community.general collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: false + source_path: ${{ env.community_general }} + - name: create kubernetes cluster uses: helm/kind-action@v1.8.0 with: diff --git a/changelogs/fragments/20250324-k8s_info-templating.yaml b/changelogs/fragments/20250324-k8s_info-templating.yaml new file mode 100644 index 00000000..6bdb17b0 --- /dev/null +++ b/changelogs/fragments/20250324-k8s_info-templating.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - action/k8s_info - update templating mechanism with changes from ``ansible-core 2.19`` (https://github.com/ansible-collections/kubernetes.core/pull/888). diff --git a/plugins/action/k8s_info.py b/plugins/action/k8s_info.py index 650e0bea..9b20a854 100644 --- a/plugins/action/k8s_info.py +++ b/plugins/action/k8s_info.py @@ -25,30 +25,18 @@ from ansible.module_utils.parsing.convert_bool import boolean from ansible.module_utils.six import iteritems, string_types from ansible.plugins.action import ActionBase +try: + from ansible.template import trust_as_template +except ImportError: + trust_as_template = None -class RemoveOmit(object): - def __init__(self, buffer, omit_value): - try: - import yaml - except ImportError: - raise AnsibleError("Failed to import the required Python library (PyYAML).") - self.data = yaml.safe_load_all(buffer) - self.omit = omit_value - def remove_omit(self, data): - if isinstance(data, dict): - result = dict() - for key, value in iteritems(data): - if value == self.omit: - continue - result[key] = self.remove_omit(value) - return result - if isinstance(data, list): - return [self.remove_omit(v) for v in data if v != self.omit] - return data - - def output(self): - return [self.remove_omit(d) for d in self.data] +def _from_yaml_to_definition(buffer): + try: + import yaml + except ImportError: + raise AnsibleError("Failed to import the required Python library (PyYAML).") + return list(yaml.safe_load_all(buffer)) ENV_KUBECONFIG_PATH_SEPARATOR = ";" if platform.system() == "Windows" else ":" @@ -207,7 +195,6 @@ class ActionModule(ActionBase): "'template' is only a supported parameter for the 'k8s' module." ) - omit_value = task_vars.get("omit") template_params = [] if isinstance(template, string_types) or isinstance(template, dict): template_params.append(self.get_template_args(template)) @@ -230,17 +217,18 @@ class ActionModule(ActionBase): old_vars = self._templar.available_variables default_environment = {} - for key in ( - "newline_sequence", - "variable_start_string", - "variable_end_string", - "block_start_string", - "block_end_string", - "trim_blocks", - "lstrip_blocks", - ): - if hasattr(self._templar.environment, key): - default_environment[key] = getattr(self._templar.environment, key) + if trust_as_template is None: + for key in ( + "newline_sequence", + "variable_start_string", + "variable_end_string", + "block_start_string", + "block_end_string", + "trim_blocks", + "lstrip_blocks", + ): + if hasattr(self._templar.environment, key): + default_environment[key] = getattr(self._templar.environment, key) for template_item in template_params: # We need to convert unescaped sequences to proper escaped sequences for Jinja2 newline_sequence = template_item["newline_sequence"] @@ -257,26 +245,35 @@ class ActionModule(ActionBase): with self.get_template_data(template_item["path"]) as template_data: # add ansible 'template' vars temp_vars = copy.deepcopy(task_vars) + overrides = {} for key, value in iteritems(template_item): if hasattr(self._templar.environment, key): if value is not None: - setattr(self._templar.environment, key, value) - else: + overrides[key] = value + if trust_as_template is None: + setattr(self._templar.environment, key, value) + elif trust_as_template is None: setattr( self._templar.environment, key, default_environment.get(key), ) self._templar.available_variables = temp_vars - result = self._templar.do_template( - template_data, - preserve_trailing_newlines=True, - escape_backslashes=False, - ) - if omit_value is not None: - result_template.extend(RemoveOmit(result, omit_value).output()) + if trust_as_template: + template_data = trust_as_template(template_data) + result = self._templar.template( + template_data, + preserve_trailing_newlines=True, + escape_backslashes=False, + overrides=overrides, + ) else: - result_template.append(result) + result = self._templar.do_template( + template_data, + preserve_trailing_newlines=True, + escape_backslashes=False, + ) + result_template.extend(_from_yaml_to_definition(result)) self._templar.available_variables = old_vars resource_definition = self._task.args.get("definition", None) if not resource_definition: diff --git a/tests/integration/targets/k8s_apply/tasks/main.yml b/tests/integration/targets/k8s_apply/tasks/main.yml index c759d7bc..fb5dc9b1 100644 --- a/tests/integration/targets/k8s_apply/tasks/main.yml +++ b/tests/integration/targets/k8s_apply/tasks/main.yml @@ -26,7 +26,8 @@ assert: that: - k8s_configmap is changed - - k8s_configmap.result.metadata.annotations|default(False) + - '"annotations" in k8s_configmap.result.metadata' + - k8s_configmap.result.metadata.annotations != {} - name: Add same configmap again k8s: @@ -467,7 +468,7 @@ assert: that: - k8s_secret is changed - - k8s_secret.result.data.foo + - k8s_secret.result.data.foo != "" - name: Add same secret k8s: @@ -748,7 +749,7 @@ assert: that: - _create is changed - - not _info.resources + - _info.resources | length == 0 # server side apply over kubernetes client releases - name: Create temporary directory diff --git a/tests/integration/targets/k8s_delete/tasks/main.yml b/tests/integration/targets/k8s_delete/tasks/main.yml index d7b144cb..e92ab706 100644 --- a/tests/integration/targets/k8s_delete/tasks/main.yml +++ b/tests/integration/targets/k8s_delete/tasks/main.yml @@ -38,7 +38,7 @@ - name: Assert that there are pods assert: that: - - pods_create.resources + - pods_create.resources | length > 0 - name: Remove the daemonset k8s: @@ -74,7 +74,7 @@ - name: Assert that deleting the daemonset deleted the pods assert: that: - - not pods_delete.resources + - pods_delete.resources | length == 0 # test deletion using label selector - name: Deploy load balancer diff --git a/tests/integration/targets/k8s_drain/tasks/main.yml b/tests/integration/targets/k8s_drain/tasks/main.yml index 5891011f..972fd902 100644 --- a/tests/integration/targets/k8s_drain/tasks/main.yml +++ b/tests/integration/targets/k8s_drain/tasks/main.yml @@ -244,7 +244,7 @@ kind: Pod name: '{{ drain_pod_name }}' register: _result - failed_when: _result.resources + failed_when: _result.resources | length > 0 - name: assert that emptyDir pod was deleted k8s_info: @@ -346,7 +346,7 @@ kind: Pod name: '{{ drain_pod_name }}-01' register: _result - failed_when: _result.resources + failed_when: _result.resources | length > 0 # test: drain using pod_selectors - name: Uncordon node diff --git a/tests/integration/targets/k8s_full/tasks/main.yml b/tests/integration/targets/k8s_full/tasks/main.yml index 10abccfc..4a0a9fa9 100644 --- a/tests/integration/targets/k8s_full/tasks/main.yml +++ b/tests/integration/targets/k8s_full/tasks/main.yml @@ -394,9 +394,11 @@ register: k8s_info_testing6 failed_when: not k8s_info_testing6.resources or k8s_info_testing6.resources[0].status.phase != "Active" - - name: Create large configmap data - command: dd if=/dev/urandom bs=500K count=1 - register: cmap_data + - name: Create a file with specific size and attributes, to be used as swap space + community.general.filesize: + path: /tmp/configmap.bin + size: 500K + source: /dev/urandom - name: Create configmap with large value k8s: @@ -407,7 +409,7 @@ name: testmap namespace: testing data: - testkey: "{{ cmap_data.stdout | b64encode }}" + testkey: "{{ lookup('file', '/tmp/configmap.bin') | b64encode }}" wait: true register: result @@ -424,7 +426,7 @@ - assert: that: - - result.resources[0].data.testkey == "{{ cmap_data.stdout | b64encode }}" + - result.resources[0].data.testkey == (lookup('file', '/tmp/configmap.bin') | b64encode) # test setting module defaults for kubernetes.core.k8s_info - block: diff --git a/tests/integration/targets/k8s_gc/tasks/main.yml b/tests/integration/targets/k8s_gc/tasks/main.yml index a2f60c8a..e8269f72 100644 --- a/tests/integration/targets/k8s_gc/tasks/main.yml +++ b/tests/integration/targets/k8s_gc/tasks/main.yml @@ -36,7 +36,7 @@ label_selectors: - "job=gc" register: wait_job - until: wait_job.resources + until: wait_job.resources | length > 0 retries: 5 delay: 10 @@ -87,7 +87,7 @@ label_selectors: - "job=gc" register: wait_job - until: wait_job.resources + until: wait_job.resources | length > 0 retries: 5 delay: 10 @@ -139,7 +139,7 @@ label_selectors: - "job=gc" register: wait_job - until: wait_job.resources + until: wait_job.resources | length > 0 retries: 5 delay: 10 @@ -225,7 +225,7 @@ - name: Assert job is deleted assert: - that: not job.resources + that: job.resources | length == 0 always: - name: Delete namespace diff --git a/tests/integration/targets/k8s_json_patch/tasks/main.yml b/tests/integration/targets/k8s_json_patch/tasks/main.yml index 314f611b..7599dbe4 100644 --- a/tests/integration/targets/k8s_json_patch/tasks/main.yml +++ b/tests/integration/targets/k8s_json_patch/tasks/main.yml @@ -47,7 +47,7 @@ - result.changed - result.result.metadata.labels.label2 == "bar" - result.result.spec.containers[0].image == "busybox:glibc" - - result.diff + - result.diff != {} - name: Describe pod kubernetes.core.k8s_info: diff --git a/tests/integration/targets/k8s_scale/tasks/main.yml b/tests/integration/targets/k8s_scale/tasks/main.yml index f5c79df5..bce2e79b 100644 --- a/tests/integration/targets/k8s_scale/tasks/main.yml +++ b/tests/integration/targets/k8s_scale/tasks/main.yml @@ -129,7 +129,7 @@ that: - scale_down is changed - '"duration" in scale_down' - - scale_down.diff + - scale_down.diff != {} - name: Scale the deployment once again (idempotency) k8s_scale: @@ -274,7 +274,7 @@ assert: that: - scale_down_no_wait is changed - - scale_down_no_wait.diff + - scale_down_no_wait.diff != {} - scale_down_no_wait_pods.resources | length == 1 # scale multiple resource using label selectors diff --git a/tests/integration/targets/k8s_taint/tasks/main.yml b/tests/integration/targets/k8s_taint/tasks/main.yml index f1a99314..c1f49ffc 100644 --- a/tests/integration/targets/k8s_taint/tasks/main.yml +++ b/tests/integration/targets/k8s_taint/tasks/main.yml @@ -421,7 +421,7 @@ - name: Assert that taints have been removed assert: that: - - _result.resources | selectattr('spec.taints', 'undefined') + - _result.resources | selectattr('spec.taints', 'undefined') | list | length > 0 always: diff --git a/tests/integration/targets/k8s_template/tasks/main.yml b/tests/integration/targets/k8s_template/tasks/main.yml index 7aedecea..58432331 100644 --- a/tests/integration/targets/k8s_template/tasks/main.yml +++ b/tests/integration/targets/k8s_template/tasks/main.yml @@ -7,7 +7,7 @@ kubernetes.core.k8s_service: template: "pod_one.j2" state: present - ignore_errors: yes + ignore_errors: true register: r - name: Check for expected failures in last tasks @@ -35,7 +35,7 @@ k8s_pod_name_one: pod k8s_pod_namespace: "{{ template_namespace }}" register: r - ignore_errors: yes + ignore_errors: true - name: Check if definition and template are mutually exclusive assert: @@ -52,7 +52,7 @@ k8s_pod_name_one: pod k8s_pod_namespace: "{{ template_namespace }}" register: r - ignore_errors: yes + ignore_errors: true - name: Check if src and template are mutually exclusive assert: @@ -63,7 +63,7 @@ - name: Create pod using template (direct specification) kubernetes.core.k8s: template: "pod_one.j2" - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_one: pod-1 @@ -79,7 +79,7 @@ kubernetes.core.k8s: template: - default - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_one: pod-2 @@ -96,7 +96,7 @@ kubernetes.core.k8s: template: path: "pod_one.j2" - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_one: pod-3 @@ -114,12 +114,11 @@ path: "pod_two.j2" variable_start_string: '[[' variable_end_string: ']]' - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_two: pod-4 - k8s_pod_namespace: "[[ template_namespace ]]" - ansible_python_interpreter: "[[ ansible_playbook_python ]]" + k8s_pod_namespace: "template-test" register: r - name: Assert that pod creation succeeded using template @@ -131,7 +130,7 @@ kubernetes.core.k8s: template: path: "pod_three.j2" - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_three_one: pod-5 @@ -152,7 +151,7 @@ variable_start_string: '[[' variable_end_string: ']]' - path: "pod_three.j2" - wait: yes + wait: true wait_timeout: "{{ k8s_wait_timeout | default(omit) }}" vars: k8s_pod_name_one: pod-7 @@ -239,63 +238,6 @@ - resource.result.results | selectattr('changed') | list | length == 1 - resource.result.results | selectattr('error', 'defined') | list | length == 1 - # Test resource definition using template with 'omit' - - name: Deploy configmap using template - k8s: - namespace: "{{ template_namespace }}" - name: test-data - template: configmap.yml.j2 - - - name: Read configmap created - k8s_info: - kind: configmap - namespace: "{{ template_namespace }}" - name: test-data - register: _configmap - - - name: Validate that the configmap does not contains annotations - assert: - that: - - '"annotations" not in _configmap.resources.0.metadata' - - - name: Create resource once again - k8s: - namespace: "{{ template_namespace }}" - name: test-data - template: configmap.yml.j2 - register: _configmap - - - name: assert that nothing changed - assert: - that: - - _configmap is not changed - - - name: Create resource once again (using description) - k8s: - namespace: "{{ template_namespace }}" - name: test-data - template: configmap.yml.j2 - register: _configmap - vars: - k8s_configmap_desc: "This is a simple configmap used to test ansible k8s collection" - - - name: assert that configmap was changed - assert: - that: - - _configmap is changed - - - name: Read configmap created - k8s_info: - kind: configmap - namespace: "{{ template_namespace }}" - name: test-data - register: _configmap - - - name: Validate that the configmap does not contains annotations - assert: - that: - - _configmap.resources.0.metadata.annotations.description == "This is a simple configmap used to test ansible k8s collection" - always: - name: Remove namespace (Cleanup) kubernetes.core.k8s: diff --git a/tests/integration/targets/k8s_template/templates/configmap.yml.j2 b/tests/integration/targets/k8s_template/templates/configmap.yml.j2 deleted file mode 100644 index bdca2e0b..00000000 --- a/tests/integration/targets/k8s_template/templates/configmap.yml.j2 +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - annotations: - description: "{{ k8s_configmap_desc | default(omit) }}" -data: - key: "testing-template" \ No newline at end of file diff --git a/tests/integration/targets/lookup_k8s/tasks/main.yml b/tests/integration/targets/lookup_k8s/tasks/main.yml index 63723b1d..ee14a31f 100644 --- a/tests/integration/targets/lookup_k8s/tasks/main.yml +++ b/tests/integration/targets/lookup_k8s/tasks/main.yml @@ -56,8 +56,8 @@ # Without wantlist=True lookup should return mapping - test5 is mapping - test6 is mapping - # errors='ignore' - - test7 is string + # errors='ignore' (return null with ansible-core 2.19) + - test7 is string or not test7 - test8 is not defined - name: Create another namespace with label diff --git a/tests/integration/targets/setup_namespace/tasks/main.yml b/tests/integration/targets/setup_namespace/tasks/main.yml index 032444e1..18c46bf9 100644 --- a/tests/integration/targets/setup_namespace/tasks/main.yml +++ b/tests/integration/targets/setup_namespace/tasks/main.yml @@ -4,10 +4,17 @@ namespace_to_create: "{{ item.name | default(item) }}" namespace_labels: "{{ item.labels | default(omit) }}" with_items: "{{ test_namespace }}" - when: test_namespace | type_debug == "list" + when: + - test_namespace is not string + - test_namespace is not mapping + - test_namespace is iterable - include_tasks: tasks/create.yml vars: namespace_to_create: "{{ test_namespace }}" namespace_labels: "{{ test_namespace_labels | default(omit) }}" - when: test_namespace | type_debug == "AnsibleUnicode" + when: + - test_namespace is string + - test_namespace is iterable + - test_namespace is sequence + - test_namespace is not mapping diff --git a/tests/unit/action/test_remove_omit.py b/tests/unit/action/test_remove_omit.py deleted file mode 100644 index e8206a44..00000000 --- a/tests/unit/action/test_remove_omit.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2022, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -from datetime import datetime - -from ansible_collections.kubernetes.core.plugins.action.k8s_info import RemoveOmit - - -def get_omit_token(): - return "__omit_place_holder__%s" % datetime.now().strftime("%Y%m%d%H%M%S") - - -def test_remove_omit_from_str(): - omit_token = get_omit_token() - src = """ - project: ansible - collection: {omit} - """.format( - omit=omit_token - ) - result = RemoveOmit(src, omit_value=omit_token).output() - assert len(result) == 1 - assert result[0] == dict(project="ansible") - - -def test_remove_omit_from_list(): - omit_token = get_omit_token() - src = """ - items: - - {omit} - """.format( - omit=omit_token - ) - result = RemoveOmit(src, omit_value=omit_token).output() - assert len(result) == 1 - assert result[0] == dict(items=[]) - - -def test_remove_omit_from_list_of_dict(): - omit_token = get_omit_token() - src = """ - items: - - owner: ansible - team: {omit} - - simple_list_item - """.format( - omit=omit_token - ) - result = RemoveOmit(src, omit_value=omit_token).output() - assert len(result) == 1 - assert result[0] == dict(items=[dict(owner="ansible"), "simple_list_item"]) - - -def test_remove_omit_combined(): - omit_token = get_omit_token() - src = """ - items: - - {omit} - - list_item_a - - list_item_b - parent: - child: - subchilda: {omit} - subchildb: - name: {omit} - age: 3 - """.format( - omit=omit_token - ) - result = RemoveOmit(src, omit_value=omit_token).output() - assert len(result) == 1 - assert result[0] == dict( - items=["list_item_a", "list_item_b"], - parent=dict(child=dict(subchildb=dict(age=3))), - ) - - -def test_remove_omit_mutiple_documents(): - omit_token = get_omit_token() - src = [ - """ - project: ansible - collection: {omit} - """.format( - omit=omit_token - ), - "---", - """ - project: kubernetes - environment: production - collection: {omit}""".format( - omit=omit_token - ), - ] - src = "\n".join(src) - print(src) - result = RemoveOmit(src, omit_value=omit_token).output() - assert len(result) == 2 - assert result[0] == dict(project="ansible") - assert result[1] == dict(project="kubernetes", environment="production")