From 259100acf8b116992849c3d1e11d3137ed06083a Mon Sep 17 00:00:00 2001 From: DilasserT Date: Fri, 6 Sep 2024 22:28:50 +0200 Subject: [PATCH] Adding podman_copy with tests --- ci/playbooks/containers/podman_copy.yml | 8 + plugins/modules/podman_copy.py | 167 ++++++++++++++ .../targets/podman_copy/tasks/main.yaml | 214 ++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 ci/playbooks/containers/podman_copy.yml create mode 100644 plugins/modules/podman_copy.py create mode 100644 tests/integration/targets/podman_copy/tasks/main.yaml diff --git a/ci/playbooks/containers/podman_copy.yml b/ci/playbooks/containers/podman_copy.yml new file mode 100644 index 00000000..473cc686 --- /dev/null +++ b/ci/playbooks/containers/podman_copy.yml @@ -0,0 +1,8 @@ +--- +- hosts: all + gather_facts: true + tasks: + - include_role: + name: podman_copy + vars: + ansible_python_interpreter: "{{ _ansible_python_interpreter }}" diff --git a/plugins/modules/podman_copy.py b/plugins/modules/podman_copy.py new file mode 100644 index 00000000..8c64e240 --- /dev/null +++ b/plugins/modules/podman_copy.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2021, Sagi Shnaidman +# 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_copy +short_description: Copy from/to containers +author: Thomas Dilasser (@DilasserT) +description: + - podman copies files from/to the container from/to the local machine. +options: + path: + description: + - Filesystem path. + type: str + required: true + container_path: + description: + - Path inside the container + type: str + required: true + into_container: + description: + - Copy in the container. + type: bool + from_container: + description: + - Copy from the container. + type: bool + container: + description: + - container name. + type: str + required: true + archive: + description: + - Chown copied file to the primary uid/gid of the destined container. + type: bool + default: true + overwrite: + description: + - Allow to overwrite directories with non-directories and vice versa + type: bool + default: true + force: + description: + - Overwrite file if it exists. + type: bool + default: false + 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 +requirements: + - "Podman installed on host" +''' + +RETURN = ''' +''' + +EXAMPLES = ''' +# What modules does for example +- containers.podman.podman_copy: + path: /path/to/file + container_path: /path/to/file/in/container + container: container-name + from_container: true +- containers.podman.podman_copy: + path: /path/to/file + container_path: /path/to/file/in/container + container: container-name + into_container: true +''' + +import os # noqa: E402 +import json +from ansible.module_utils.basic import AnsibleModule # noqa: E402 + + +def copy(module, executable): + changed = True + command = [executable, 'cp'] + if not module.params['archive']: + command += ["--archive=false"] + if module.params['overwrite']: + command += ["--overwrite=true"] + container_name = module.params['container'] + if module.params['into_container']: + command += [module.params['path']] + command += [module.params['container'] + ":" + module.params['container_path']] + command_inspect = [executable, 'container', 'inspect'] + command_inspect.extend([container_name]) + rc, out, err = module.run_command(command_inspect) + if rc != 0: + module.fail_json(msg="Unable to gather info for %s: %s" % (",".join(module.params['container']), err)) + else: + json_out = json.loads(out) if out else None + if json_out is None: + module.fail_json(msg="Unable to gather info for %s: %s" % (",".join(module.params['container']), err)) + + full_path = json_out[0]['GraphDriver']['Data']['MergedDir'] + if module.params['path'][0] == "/": + full_path += module.params['container_path'] + else: + full_path += "/" + full_path += module.params['container_path'] + + if os.path.exists(full_path) and not module.params['force']: + changed = False + return changed, '', '' + + else: + command += [module.params['container'] + ":" + module.params['container_path']] + command += [module.params['path']] + if os.path.exists(module.params['path']) and not module.params['force']: + changed = False + return changed, '', '' + + rc, out, err = module.run_command(command) + if rc != 0: + module.fail_json(msg="Error during copy %s: %s" % ( + module.params['container'], err)) + return changed, out, err + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='str', required=True), + container_path=dict(type='str', required=True), + container=dict(type='str', required=True), + into_container=dict(type='bool'), + from_container=dict(type='bool'), + force=dict(type='bool', default=False), + archive=dict(type='bool', default=True), + overwrite=dict(type='bool', default=False), + executable=dict(type='str', default='podman') + ), + supports_check_mode=True, + mutually_exclusive=[ + ('from_container', 'into_container'), + ], + required_one_of=[ + ('from_container', 'into_container'), + ], + ) + + executable = module.get_bin_path(module.params['executable'], required=True) + changed, out, err = copy(module, executable) + + results = { + "changed": changed, + "stdout": out, + "stderr": err, + } + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/podman_copy/tasks/main.yaml b/tests/integration/targets/podman_copy/tasks/main.yaml new file mode 100644 index 00000000..f374f63f --- /dev/null +++ b/tests/integration/targets/podman_copy/tasks/main.yaml @@ -0,0 +1,214 @@ +--- +- name: Test podman copy + block: + - name: run container + containers.podman.podman_container: + name: container + image: alpine:3.7 + user: 999:999 + state: started + command: sleep 1d + + - name: Create test file + file: + path: /tmp/podman_copy.txt + state: touch + + - name: Get container info + containers.podman.podman_container_info: + name: container + register: cntr_info + + - name: Copy into container + containers.podman.podman_copy: + path: '/tmp/podman_copy.txt' + container_path: '/podman_copy.txt' + container: container + into_container: true + register: copy1 + + - name: Check first copy + assert: + that: + - copy1 is success + - copy1 is changed + + - name: Stat file in container + stat: + path: "{{ cntr_info['containers'][0]['GraphDriver']['Data']['MergedDir'] }}/podman_copy.txt" + register: file_check + + - name: check file is in container with container perms + assert: + that: + - file_check.stat.islnk is defined + - file_check.stat.islnk == False + - file_check.stat.uid == 999 + - file_check.stat.gid == 999 + + - name: Copy into container without force + containers.podman.podman_copy: + path: '/tmp/podman_copy.txt' + container_path: '/podman_copy.txt' + container: container + into_container: true + register: copy2 + + - name: Check second copy + assert: + that: + - copy2 is success + - copy2 is not changed + + - name: Copy into container (forced) + containers.podman.podman_copy: + path: '/tmp/podman_copy.txt' + container_path: '/podman_copy.txt' + container: container + force: true + into_container: true + register: copy3 + + - name: Check third copy + assert: + that: + - copy3 is success + - copy3 is changed + + - name: Copy into container without archive (forced) + containers.podman.podman_copy: + path: '/tmp/podman_copy.txt' + container_path: '/podman_copy.txt' + container: container + force: true + archive: false + into_container: true + register: copy4 + + - name: Check third copy + assert: + that: + - copy4 is success + - copy4 is changed + + - name: Stat file in container for archive test + stat: + path: "{{ cntr_info['containers'][0]['GraphDriver']['Data']['MergedDir'] }}/podman_copy.txt" + register: file_check + + - name: check file is in container and has root perms + assert: + that: + - file_check.stat.islnk is defined + - file_check.stat.uid == 0 + - file_check.stat.gid == 0 + + - name: Create overwrite file + file: + path: "/tmp/overwrite_obj" + state: touch + + - name: Create file to overwrite in container + containers.podman.podman_container_exec: + name: container + command: "mkdir /tmp/overwrite_obj" + + - name: Copy file on dir in container with overwrite + containers.podman.podman_copy: + path: '/tmp/overwrite_obj' + container_path: '/tmp/' + container: container + overwrite: true + force: true + into_container: true + register: copy5 + + - name: Check fifth copy + assert: + that: + - copy5 is success + - copy5 is changed + + - name: Stat dir in container + stat: + path: "{{ cntr_info['containers'][0]['GraphDriver']['Data']['MergedDir'] }}/tmp/overwrite_obj" + register: file_check + + - name: check file is in container and has root perms + assert: + that: + - file_check.stat.islnk is defined + - file_check.stat.islnk == False + - file_check.stat.isdir == False + + - name: Copy from container + containers.podman.podman_copy: + path: '/tmp/podman_copy_from.txt' + container_path: '/podman_copy.txt' + container: container + from_container: true + register: copy6 + + - name: Stat file in /tmp + stat: + path: "/tmp/podman_copy_from.txt" + register: file_check + + - name: Check sixth copy + assert: + that: + - copy6 is success + - copy6 is changed + + - name: Check file is in container + assert: + that: + - file_check.stat.islnk is defined + - file_check.stat.islnk == False + + - name: Copy from container without force + containers.podman.podman_copy: + path: '/tmp/podman_copy_from.txt' + container_path: '/podman_copy.txt' + container: container + from_container: true + register: copy7 + + - name: Check seventh copy + assert: + that: + - copy7 is success + - copy7 is not changed + + - name: Copy from container (forced) + containers.podman.podman_copy: + path: '/tmp/podman_copy_from.txt' + container_path: '/podman_copy.txt' + container: container + force: true + from_container: true + register: copy8 + + - name: Check eighth copy + assert: + that: + - copy8 is success + - copy8 is changed + + always: + - name: stop container + containers.podman.podman_container: + state: absent + name: container + - name: Clean up test file + file: + path: /tmp/podman_copy.txt + state: absent + - name: Clean up test file + file: + path: /tmp/podman_copy_from.txt + state: absent + - name: Clean up test dir + file: + path: /tmp/overwrite_dir + state: absent