From a32108f64fb80d94bf81906a1dfb671eded7cf07 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:07:52 +0000 Subject: [PATCH] Added support for copying files to init Containers. (#971) (#1003) This is a backport of PR #971 as merged into main (027700c). SUMMARY Was going trough the list with issues and found 958; which seemed a quick fix. What I fixed with with this PR: Added support for copying files to init containers. Fixed the format message when an exec is failing for a pod (the order was wrong). Added a check if the container that you try to run copy for is started. ISSUE TYPE Bugfix Pull Request COMPONENT NAME copy.py module ADDITIONAL INFORMATION Some testing. Verify that the pod does not exist: kubectl -n default get pod/yorick Output: Error from server (NotFound): pods "yorick" not found Run the playbook to create the file, create the deployment, wait for the init container to be ready, copy the created file to the init container, cat the copied file (using kubernetes.core.k8s_exec) that is now in the init container and try to copy the created file to the (not started) container (which fails - to see the new error message for it): cat << EOF | ansible-playbook /dev/stdin - hosts: localhost gather_facts: False tasks: - ansible.builtin.copy: content: | Hi there dest: /tmp/yorick.txt - name: Deploy pod with initContainer with an unlimited while loop kubernetes.core.k8s: kubeconfig: "~/.kube/config" definition: apiVersion: v1 kind: Pod metadata: name: "yorick" namespace: "default" spec: initContainers: - name: "yorick-init" image: busybox:latest command: ["/bin/sh"] args: - "-c" - | echo "Init container started, waiting for file..." # Wait for the file to be copied while :;do echo "Waiting for file" sleep 5 done echo "File received! Init container completing..." containers: - name: "yorick-container" image: busybox:latest command: ["/bin/sh"] args: - "-c" - | # Keep container running for testing sleep 300 - kubernetes.core.k8s_info: kubeconfig: "~/.kube/config" api_version: v1 kind: Pod name: "yorick" namespace: "default" register: pod_status until: >- pod_status.resources|length > 0 and 'initContainerStatuses' in pod_status.resources.0.status and pod_status.resources.0.status.initContainerStatuses|length > 0 and pod_status.resources.0.status.initContainerStatuses.0.started|bool - name: Copy /tmp/yorick.txt to the yorick-init init container kubernetes.core.k8s_cp: kubeconfig: "~/.kube/config" namespace: default pod: yorick remote_path: /tmp/yorick.txt local_path: /tmp/yorick.txt container: yorick-init - name: Execute a command kubernetes.core.k8s_exec: kubeconfig: "~/.kube/config" namespace: default pod: yorick container: yorick-init command: cat /tmp/yorick.txt register: exec_out - ansible.builtin.debug: var: exec_out.stdout - name: Try to copy /tmp/yorick.txt to the yorick-container container kubernetes.core.k8s_cp: kubeconfig: "~/.kube/config" namespace: default pod: yorick remote_path: /tmp/yorick.txt local_path: /tmp/yorick.txt container: yorick-container EOF Output: PLAY [localhost] ******************************************************************************************************************************************************************** TASK [ansible.builtin.copy] ********************************************************************************************************************************************************* Thursday 31 July 2025 02:01:21 +0200 (0:00:00.016) 0:00:00.016 ********* ok: [localhost] TASK [Deploy pod with initContainer with an unlimited while loop] ******************************************************************************************************************* Thursday 31 July 2025 02:01:21 +0200 (0:00:00.788) 0:00:00.804 ********* changed: [localhost] TASK [kubernetes.core.k8s_info] ***************************************************************************************************************************************************** Thursday 31 July 2025 02:01:25 +0200 (0:00:03.963) 0:00:04.768 ********* FAILED - RETRYING: [localhost]: kubernetes.core.k8s_info (3 retries left). ok: [localhost] TASK [Copy /tmp/yorick.txt to the yorick-init init container] *********************************************************************************************************************** Thursday 31 July 2025 02:01:32 +0200 (0:00:06.598) 0:00:11.366 ********* changed: [localhost] TASK [Execute a command] ************************************************************************************************************************************************************ Thursday 31 July 2025 02:01:39 +0200 (0:00:07.017) 0:00:18.383 ********* changed: [localhost] TASK [ansible.builtin.debug] ******************************************************************************************************************************************************** Thursday 31 July 2025 02:01:40 +0200 (0:00:00.644) 0:00:19.028 ********* ok: [localhost] => { "exec_out.stdout": "Hi there\n" } TASK [Try to copy /tmp/yorick.txt to the yorick-container container] **************************************************************************************************************** Thursday 31 July 2025 02:01:40 +0200 (0:00:00.021) 0:00:19.050 ********* fatal: [localhost]: FAILED! => { "changed": false } MSG: Pod container yorick-container is not started PLAY RECAP ************************************************************************************************************************************************************************** localhost : ok=6 changed=3 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Playbook run took 0 days, 0 hours, 0 minutes, 21 seconds Reviewed-by: Bianca Henderson --- .../20250731-fix-k8s_cp-initcontainers.yaml | 3 +++ plugins/module_utils/copy.py | 22 +++++++++++----- plugins/modules/k8s_cp.py | 1 + .../targets/k8s_copy/defaults/main.yml | 6 +++++ .../targets/k8s_copy/tasks/main.yml | 18 +++++++++++++ .../k8s_copy/tasks/test_copy_errors.yml | 18 +++++++++++++ .../tasks/test_init_container_pod.yml | 25 +++++++++++++++++++ .../templates/pods_definition_init.j2 | 20 +++++++++++++++ 8 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/20250731-fix-k8s_cp-initcontainers.yaml create mode 100644 tests/integration/targets/k8s_copy/tasks/test_init_container_pod.yml create mode 100644 tests/integration/targets/k8s_copy/templates/pods_definition_init.j2 diff --git a/changelogs/fragments/20250731-fix-k8s_cp-initcontainers.yaml b/changelogs/fragments/20250731-fix-k8s_cp-initcontainers.yaml new file mode 100644 index 00000000..b02d3153 --- /dev/null +++ b/changelogs/fragments/20250731-fix-k8s_cp-initcontainers.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - Update the `k8s_cp` module to also work for init containers (https://github.com/ansible-collections/kubernetes.core/pull/971). diff --git a/plugins/module_utils/copy.py b/plugins/module_utils/copy.py index c70eed1a..0e19e17d 100644 --- a/plugins/module_utils/copy.py +++ b/plugins/module_utils/copy.py @@ -96,7 +96,7 @@ class K8SCopy(metaclass=ABCMeta): return error, stdout, stderr except Exception as e: self.module.fail_json( - msg="Error while running/parsing from pod {1}/{2} command='{0}' : {3}".format( + msg="Error while running/parsing from pod {0}/{1} command='{2}' : {3}".format( self.namespace, self.name, cmd, to_native(e) ) ) @@ -435,11 +435,21 @@ def check_pod(svc): try: result = svc.client.get(resource, name=name, namespace=namespace) - containers = [ - c["name"] for c in result.to_dict()["status"]["containerStatuses"] - ] - if container and container not in containers: + containers = dict( + { + c["name"]: c + for cl in ["initContainerStatuses", "containerStatuses"] + for c in result.to_dict()["status"].get(cl, []) + } + ) + if container and container not in containers.keys(): module.fail_json(msg="Pod has no container {0}".format(container)) - return containers + if ( + container + and container in containers + and not bool(containers[container].get("started", False)) + ): + module.fail_json(msg="Pod container {0} is not started".format(container)) + return containers.keys() except Exception as exc: _fail(exc) diff --git a/plugins/modules/k8s_cp.py b/plugins/modules/k8s_cp.py index 16f0f8cb..b18ca556 100644 --- a/plugins/modules/k8s_cp.py +++ b/plugins/modules/k8s_cp.py @@ -79,6 +79,7 @@ options: notes: - the tar binary is required on the container when copying from local filesystem to pod. + - the (init) container has to be started before you copy files or directories to it. """ EXAMPLES = r""" diff --git a/tests/integration/targets/k8s_copy/defaults/main.yml b/tests/integration/targets/k8s_copy/defaults/main.yml index aaf46330..7c82d25d 100644 --- a/tests/integration/targets/k8s_copy/defaults/main.yml +++ b/tests/integration/targets/k8s_copy/defaults/main.yml @@ -14,3 +14,9 @@ pod_with_two_container: pod_without_executable_find: name: openjdk-pod + +pod_with_initcontainer_and_container: + name: pod-copy-2 + container: + - container-20 + - container-21 diff --git a/tests/integration/targets/k8s_copy/tasks/main.yml b/tests/integration/targets/k8s_copy/tasks/main.yml index 519be8f6..62cced82 100644 --- a/tests/integration/targets/k8s_copy/tasks/main.yml +++ b/tests/integration/targets/k8s_copy/tasks/main.yml @@ -18,6 +18,23 @@ wait: yes template: pods_definition.j2 + - name: Create Init Pod + k8s: + namespace: '{{ copy_namespace }}' + template: pods_definition_init.j2 + + - kubernetes.core.k8s_info: + api_version: v1 + kind: Pod + name: '{{ pod_with_initcontainer_and_container.name }}' + namespace: '{{ copy_namespace }}' + register: init_pod_status + until: >- + init_pod_status.resources|length > 0 + and 'initContainerStatuses' in init_pod_status.resources.0.status + and init_pod_status.resources.0.status.initContainerStatuses|length > 0 + and init_pod_status.resources.0.status.initContainerStatuses.0.started|bool + - include_tasks: test_copy_errors.yml - include_tasks: test_check_mode.yml - include_tasks: test_copy_file.yml @@ -25,6 +42,7 @@ - include_tasks: test_copy_directory.yml - include_tasks: test_copy_large_file.yml - include_tasks: test_copy_item_with_space_in_its_name.yml + - include_tasks: test_init_container_pod.yml always: diff --git a/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml b/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml index b1e799bf..0064e8ac 100644 --- a/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml +++ b/tests/integration/targets/k8s_copy/tasks/test_copy_errors.yml @@ -67,3 +67,21 @@ that: - copy_fake_container is failed - copy_fake_container.msg == "Pod has no container this_is_a_fake_container" + +# copy file to not started container in pod should fail +- name: copy file to not started container in pod should fail + k8s_cp: + namespace: '{{ copy_namespace }}' + pod: '{{ pod_with_initcontainer_and_container.name }}' + remote_path: /tmp + local_path: files/simple_file.txt + state: to_pod + container: '{{ pod_with_initcontainer_and_container.container[1] }}' + ignore_errors: true + register: copy_not_started_container + +- name: check that error message is as expected + assert: + that: + - copy_not_started_container is failed + - copy_not_started_container.msg == "Pod container {{ pod_with_initcontainer_and_container.container[1] }} is not started" diff --git a/tests/integration/targets/k8s_copy/tasks/test_init_container_pod.yml b/tests/integration/targets/k8s_copy/tasks/test_init_container_pod.yml new file mode 100644 index 00000000..4b5ad66f --- /dev/null +++ b/tests/integration/targets/k8s_copy/tasks/test_init_container_pod.yml @@ -0,0 +1,25 @@ +--- +- set_fact: + random_content: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits,punctuation length=128') }}" + +- name: Copy content into init container + k8s_cp: + namespace: '{{ copy_namespace }}' + pod: '{{ pod_with_initcontainer_and_container.name }}' + remote_path: /file_from_localhost.txt + content: '{{ random_content }}' + container: '{{ pod_with_initcontainer_and_container.container[0] }}' + state: to_pod + +- name: Get the content from copied file + kubernetes.core.k8s_exec: + namespace: '{{ copy_namespace }}' + pod: '{{ pod_with_initcontainer_and_container.name }}' + container: '{{ pod_with_initcontainer_and_container.container[0] }}' + command: cat /file_from_localhost.txt + register: exec_out + +- name: check that content is found and the same as generated earlier + assert: + that: + - exec_out.stdout == random_content diff --git a/tests/integration/targets/k8s_copy/templates/pods_definition_init.j2 b/tests/integration/targets/k8s_copy/templates/pods_definition_init.j2 new file mode 100644 index 00000000..0b1524cf --- /dev/null +++ b/tests/integration/targets/k8s_copy/templates/pods_definition_init.j2 @@ -0,0 +1,20 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: '{{ pod_with_initcontainer_and_container.name }}' +spec: + initContainers: + - name: '{{ pod_with_initcontainer_and_container.container[0] }}' + image: busybox + command: + - /bin/sh + - -c + - while true;do date;sleep 5; done + containers: + - name: '{{ pod_with_initcontainer_and_container.container[1] }}' + image: busybox + command: + - /bin/sh + - -c + - while true;do date;sleep 5; done