From 23becece53127f22d3b1e98531a7dd58eb17ab62 Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Thu, 1 Nov 2018 15:14:18 +0100 Subject: [PATCH] Implement connection plugin for podman (#47519) * new connection plugin: podman Signed-off-by: Tomas Tomecek * podman,conn: utilize remote_user to run commands Signed-off-by: Tomas Tomecek * podman connection: update docs Co-Authored-By: TomasTomecek --- lib/ansible/plugins/connection/podman.py | 152 ++++++++++++++++++ .../targets/connection_podman/aliases | 2 + .../targets/connection_podman/posix.sh | 1 + .../targets/connection_podman/runme.sh | 7 + .../test_connection.inventory | 15 ++ 5 files changed, 177 insertions(+) create mode 100644 lib/ansible/plugins/connection/podman.py create mode 100644 test/integration/targets/connection_podman/aliases create mode 120000 test/integration/targets/connection_podman/posix.sh create mode 100755 test/integration/targets/connection_podman/runme.sh create mode 100644 test/integration/targets/connection_podman/test_connection.inventory diff --git a/lib/ansible/plugins/connection/podman.py b/lib/ansible/plugins/connection/podman.py new file mode 100644 index 00000000000..cc342a39948 --- /dev/null +++ b/lib/ansible/plugins/connection/podman.py @@ -0,0 +1,152 @@ +# Based on the buildah connection plugin +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Connection plugin to interact with existing podman containers. +# https://github.com/containers/libpod +# +# Written by: Tomas Tomecek (https://github.com/TomasTomecek) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import shlex +import shutil +import subprocess + +from ansible.module_utils._text import to_bytes, to_native +from ansible.plugins.connection import ConnectionBase, ensure_connect + + +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + + +DOCUMENTATION = """ + author: Tomas Tomecek (ttomecek@redhat.com) + connection: podman + short_description: Interact with an existing podman container + description: + - Run commands or put/fetch files to an existing container using podman tool. + version_added: 2.8 + options: + remote_addr: + description: + - The ID of the container you want to access. + default: inventory_hostname + vars: + - name: ansible_host + remote_user: + description: + - User specified via name or UID which is used to execute commands inside the container. If you + specify the user via UID, you must set C(ANSIBLE_REMOTE_TMP) to a path that exits + inside the container and is writable by Ansible. + ini: + - section: defaults + key: remote_user + env: + - name: ANSIBLE_REMOTE_USER + vars: + - name: ansible_user +""" + + +# this _has to be_ named Connection +class Connection(ConnectionBase): + """ + This is a connection plugin for podman. It uses podman binary to interact with the containers + """ + + # String used to identify this Connection class from other classes + transport = 'podman' + + def __init__(self, play_context, new_stdin, *args, **kwargs): + super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) + + self._container_id = self._play_context.remote_addr + self._connected = False + # container filesystem will be mounted here on host + self._mount_point = None + self.user = self._play_context.remote_user + + def _podman(self, cmd, cmd_args=None, in_data=None): + """ + run podman executable + + :param cmd: podman's command to execute (str) + :param cmd_args: list of arguments to pass to the command (list of str/bytes) + :param in_data: data passed to podman's stdin + :return: return code, stdout, stderr + """ + local_cmd = ['podman', cmd, self._container_id] + if cmd_args: + local_cmd += cmd_args + local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] + + display.vvv("RUN %s" % (local_cmd,), host=self._container_id) + p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + stdout, stderr = p.communicate(input=in_data) + stdout = to_bytes(stdout, errors='surrogate_or_strict') + stderr = to_bytes(stderr, errors='surrogate_or_strict') + return p.returncode, stdout, stderr + + def _connect(self): + """ + no persistent connection is being maintained, mount container's filesystem + so we can easily access it + """ + super(Connection, self)._connect() + rc, self._mount_point, stderr = self._podman("mount") + self._mount_point = self._mount_point.strip() + display.vvvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr)) + self._connected = True + + @ensure_connect + def exec_command(self, cmd, in_data=None, sudoable=False): + """ run specified command in a running OCI container using podman """ + super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) + + # shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3 + cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict')) + if self.user: + cmd_args_list += ["--user", self.user] + + rc, stdout, stderr = self._podman("exec", cmd_args_list) + + display.vvvvv("STDOUT %r STDERR %r" % (stderr, stderr)) + return rc, stdout, stderr + + def put_file(self, in_path, out_path): + """ Place a local file located in 'in_path' inside container at 'out_path' """ + super(Connection, self).put_file(in_path, out_path) + display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._container_id) + + real_out_path = self._mount_point + to_bytes(out_path, errors='surrogate_or_strict') + shutil.copyfile( + to_bytes(in_path, errors='surrogate_or_strict'), + to_bytes(real_out_path, errors='surrogate_or_strict') + ) + + def fetch_file(self, in_path, out_path): + """ obtain file specified via 'in_path' from the container and place it at 'out_path' """ + super(Connection, self).fetch_file(in_path, out_path) + display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._container_id) + + real_in_path = self._mount_point + to_bytes(in_path, errors='surrogate_or_strict') + shutil.copyfile( + to_bytes(real_in_path, errors='surrogate_or_strict'), + to_bytes(out_path, errors='surrogate_or_strict') + ) + + def close(self): + """ unmount container's filesystem """ + super(Connection, self).close() + # we actually don't need to unmount since the container is mounted anyway + # rc, stdout, stderr = self._podman("umount") + # display.vvvvv("RC %s STDOUT %r STDERR %r" % (rc, stdout, stderr)) + self._connected = False diff --git a/test/integration/targets/connection_podman/aliases b/test/integration/targets/connection_podman/aliases new file mode 100644 index 00000000000..33b258daef2 --- /dev/null +++ b/test/integration/targets/connection_podman/aliases @@ -0,0 +1,2 @@ +non_local +unsupported diff --git a/test/integration/targets/connection_podman/posix.sh b/test/integration/targets/connection_podman/posix.sh new file mode 120000 index 00000000000..70aa5dbdba4 --- /dev/null +++ b/test/integration/targets/connection_podman/posix.sh @@ -0,0 +1 @@ +../connection_posix/test.sh \ No newline at end of file diff --git a/test/integration/targets/connection_podman/runme.sh b/test/integration/targets/connection_podman/runme.sh new file mode 100755 index 00000000000..d12223e412d --- /dev/null +++ b/test/integration/targets/connection_podman/runme.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -eux + +./posix.sh "$@" + +ANSIBLE_REMOTE_TMP="/tmp" ANSIBLE_REMOTE_USER="1000" ./posix.sh "$@" diff --git a/test/integration/targets/connection_podman/test_connection.inventory b/test/integration/targets/connection_podman/test_connection.inventory new file mode 100644 index 00000000000..04c5bcae953 --- /dev/null +++ b/test/integration/targets/connection_podman/test_connection.inventory @@ -0,0 +1,15 @@ +[podman] +podman-container +[podman:vars] +# 1. install podman +# 2. create container: +# podman pull python:3-alpine +# podman run -d --name podman-container python:3-alpine sleep 999999 +# 3. run test: +# ./bin/ansible-test integration connection_podman +# 6. remove container +# podman stop podman-container +# podman rm podman-container +ansible_host=podman-container +ansible_connection=podman +ansible_python_interpreter=/usr/local/bin/python