diff --git a/.github/workflows/podman_container_copy.yml b/.github/workflows/podman_container_copy.yml new file mode 100644 index 00000000..87c43542 --- /dev/null +++ b/.github/workflows/podman_container_copy.yml @@ -0,0 +1,99 @@ +name: Podman Container Copy module + +on: + push: + paths: + - '.github/workflows/podman_container_copy.yml' + - 'ci/*.yml' + - 'ci/run_containers_tests.sh' + - 'ci/playbooks/containers/podman_container_copy.yml' + - 'plugins/modules/podman_container_copy.py' + - 'tests/integration/targets/podman_container_copy/**' + branches: + - master + pull_request: + paths: + - '.github/workflows/podman_container_copy.yml' + - 'ci/*.yml' + - 'ci/run_containers_tests.sh' + - 'ci/playbooks/containers/podman_container_copy.yml' + - 'plugins/modules/podman_container_copy.py' + - 'tests/integration/targets/podman_container_copy/**' + schedule: + - cron: 4 0 * * * # Run daily at 0:03 UTC + +jobs: + + test_podman_container_copy: + name: Podman Container Copy ${{ matrix.ansible-version }}-${{ matrix.os || 'ubuntu-22.04' }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + ansible-version: + - ansible<2.10 + # - git+https://github.com/ansible/ansible.git@stable-2.11 + - git+https://github.com/ansible/ansible.git@devel + os: + - ubuntu-22.04 + python-version: + - 3.11 + + steps: + + - name: Check out repository + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Upgrade pip and display Python and PIP versions + run: | + sudo apt-get update + sudo apt-get install -y python*-wheel python*-yaml + python -m pip install --upgrade pip + python -V + pip --version + - name: Set up pip cache + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.ref }}-units-VMs + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: python3 -m pip install --user --force-reinstall --upgrade '${{ matrix.ansible-version }}' + + - name: Build and install the collection tarball + run: | + rm -rf /tmp/just_new_collection + ~/.local/bin/ansible-galaxy collection build --output-path /tmp/just_new_collection --force + ~/.local/bin/ansible-galaxy collection install -vvv --force /tmp/just_new_collection/*.tar.gz + - name: Run collection tests for Podman Container Copy module + run: | + export PATH=~/.local/bin:$PATH + echo "Run ansible version" + command -v ansible + ansible --version + export ANSIBLE_CONFIG=$(pwd)/ci/ansible-dev.cfg + if [[ '${{ matrix.ansible-version }}' == 'ansible<2.10' ]]; then + export ANSIBLE_CONFIG=$(pwd)/ci/ansible-2.9.cfg + fi + echo $ANSIBLE_CONFIG + command -v ansible-playbook + pip --version + python --version + ansible-playbook --version + ansible-playbook -vv ci/playbooks/pre.yml \ + -e host=localhost \ + -i localhost, \ + -e ansible_connection=local \ + -e setup_python=false + TEST2RUN=podman_container_copy ./ci/run_containers_tests.sh + shell: bash diff --git a/ci/playbooks/containers/podman_container_copy.yml b/ci/playbooks/containers/podman_container_copy.yml new file mode 100644 index 00000000..608cd7ff --- /dev/null +++ b/ci/playbooks/containers/podman_container_copy.yml @@ -0,0 +1,8 @@ +--- +- hosts: all + gather_facts: true + tasks: + - include_role: + name: podman_container_copy + vars: + ansible_python_interpreter: "{{ _ansible_python_interpreter }}" diff --git a/plugins/modules/podman_container_copy.py b/plugins/modules/podman_container_copy.py new file mode 100644 index 00000000..880b2489 --- /dev/null +++ b/plugins/modules/podman_container_copy.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# Copyright (c) 2024 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 + + +DOCUMENTATION = r''' +module: podman_container_copy +author: + - Alessandro Rossi (@kubealex) +short_description: Copy file to/from a container +notes: + - Podman may required elevated privileges in order to run properly. +description: + - Copy file or folder from the host to a container and vice-versa. +options: + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the machine running C(podman) + default: 'podman' + type: str + src: + description: + - Path of the file/folder to copy from/to the container + type: str + required: True + dest: + description: + - Path of the destination file/folder to copy from/to the container + required: True + type: str + container: + description: + - Name/ID of the container to copy from/to + required: True + type: str + from_container: + description: + - Specify whether or not the file must be copied from the container to the host + required: False + default: False + type: bool + archive: + description: + - Chown copied files to the primary uid/gid of the destination container. + required: False + default: True + type: bool + overwrite: + description: + - Allow to overwrite directories with non-directories and vice versa + required: False + default: False + type: bool +''' + +EXAMPLES = r""" +- name: Copy file "test.yml" on the host to the "apache" container's root folder + containers.podman.podman_search: + src: test.yml + dest: / + container: apache +- name: Copy file "test.yml" in the "apache" container's root folder to the playbook's folder + containers.podman.podman_search: + src: /test.yml + dest: ./ + container: apache + from_container: True +""" + +from ansible.module_utils.basic import AnsibleModule + + +def copy_file(module, executable, src, dest, container, from_container, archive, overwrite): + if from_container: + command = [executable, 'cp', '{0}:{1}'.format(container, src), dest] + else: + command = [executable, 'cp', src, '{0}:{1}'.format(container, dest)] + + if not archive: + command.append('--archive=False') + + if overwrite: + command.append('--overwrite') + + rc, out, err = module.run_command(command) + + if rc != 0: + module.fail_json(msg='Unable to copy file to/from container - {out}'.format(out=err)) + else: + changed = True + return changed, out, err + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + src=dict(type='str', required=True), + dest=dict(type='str', required=True), + container=dict(type='str', required=True), + from_container=dict(type='bool', required=False, default=False), + archive=dict(type='bool', required=False, default=True), + overwrite=dict(type='bool', required=False, default=False) + ), + supports_check_mode=False, + ) + + executable = module.params['executable'] + src = module.params['src'] + dest = module.params['dest'] + container = module.params['container'] + from_container = module.params['from_container'] + archive = module.params['archive'] + overwrite = module.params['overwrite'] + + executable = module.get_bin_path(executable, required=True) + + changed, out, err = copy_file(module, executable, src, dest, container, from_container, archive, overwrite) + + results = dict( + changed=changed + ) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/podman_container_copy/tasks/main.yml b/tests/integration/targets/podman_container_copy/tasks/main.yml new file mode 100644 index 00000000..db1d9c73 --- /dev/null +++ b/tests/integration/targets/podman_container_copy/tasks/main.yml @@ -0,0 +1,104 @@ +--- +- name: Test podman container copy host-container + block: + - name: Generate random value for container name + ansible.builtin.set_fact: + container_name: "{{ 'ansible-test-podman-%0x' % ((2**32) | random) }}" + file_name: sample_file + + - name: Start container + containers.podman.podman_container: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + image: alpine:3.7 + state: started + command: sleep 1d + + - name: Create local file to copy + ansible.builtin.copy: + content: | + This file tests if host -> container copy is working + dest: "{{ playbook_dir }}/{{ file_name }}" + mode: "0755" + + - name: Copy local file to containers root folder + containers.podman.podman_container_copy: + executable: "{{ test_executable | default('podman') }}" + src: "{{ playbook_dir }}/{{ file_name }}" + dest: "/{{ file_name }}" + container: "{{ container_name }}" + + - name: Verify that the file exists in the container + containers.podman.podman_container_exec: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + command: "cat /{{ file_name }}" + + always: + - name: Remove container + containers.podman.podman_container: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + state: absent + + - name: Remove local file + ansible.builtin.file: + path: "{{ playbook_dir }}/{{ file_name }}" + state: absent + +- name: Test podman container copy container-host + block: + - name: Generate random value for container name + ansible.builtin.set_fact: + container_name: "{{ 'ansible-test-podman-%0x' % ((2**32) | random) }}" + file_name: sample_file + + - name: Start container + containers.podman.podman_container: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + image: alpine:3.7 + state: started + command: sleep 1d + + - name: Create file in the container for further copy + containers.podman.podman_container_exec: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + command: sh -c 'echo "This file tests if container -> host copy is working" > /{{ file_name }}' + + - name: Verify that the file exists in the container + containers.podman.podman_container_exec: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + command: "cat /{{ file_name }}" + + - name: Copy local file to containers root folder + containers.podman.podman_container_copy: + executable: "{{ test_executable | default('podman') }}" + src: "/{{ file_name }}" + dest: "{{ playbook_dir }}/{{ file_name }}" + container: "{{ container_name }}" + from_container: true + + - name: Check file + ansible.builtin.stat: + path: "{{ playbook_dir }}/{{ file_name }}" + register: copied_file + + - name: Check it's present + ansible.builtin.assert: + that: + - copied_file.stat.exists + + always: + - name: Remove container + containers.podman.podman_container: + executable: "{{ test_executable | default('podman') }}" + name: "{{ container_name }}" + state: absent + + - name: Remove local file + ansible.builtin.file: + path: "{{ playbook_dir }}/{{ file_name }}" + state: absent