diff options
Diffstat (limited to 'lib/ansible/plugins/connection')
-rw-r--r-- | lib/ansible/plugins/connection/chroot.py | 208 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/docker.py | 357 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/funcd.py | 103 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/iocage.py | 83 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/jail.py | 202 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/kubectl.py | 356 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/libvirt_lxc.py | 182 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/lxc.py | 229 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/lxd.py | 126 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/oc.py | 174 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/qubes.py | 160 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/saltstack.py | 106 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/zone.py | 201 |
13 files changed, 0 insertions, 2487 deletions
diff --git a/lib/ansible/plugins/connection/chroot.py b/lib/ansible/plugins/connection/chroot.py deleted file mode 100644 index d95497b42b..0000000000 --- a/lib/ansible/plugins/connection/chroot.py +++ /dev/null @@ -1,208 +0,0 @@ -# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> -# -# (c) 2013, Maykel Moya <mmoya@speedyrails.com> -# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# Copyright (c) 2017 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 = """ - author: Maykel Moya <mmoya@speedyrails.com> - connection: chroot - short_description: Interact with local chroot - description: - - Run commands or put/fetch files to an existing chroot on the Ansible controller. - version_added: "1.1" - options: - remote_addr: - description: - - The path of the chroot you want to access. - default: inventory_hostname - vars: - - name: ansible_host - executable: - description: - - User specified executable shell - ini: - - section: defaults - key: executable - env: - - name: ANSIBLE_EXECUTABLE - vars: - - name: ansible_executable - default: /bin/sh - chroot_exe: - version_added: '2.8' - description: - - User specified chroot binary - ini: - - section: chroot_connection - key: exe - env: - - name: ANSIBLE_CHROOT_EXE - vars: - - name: ansible_chroot_exe - default: chroot -""" - -import os -import os.path -import subprocess -import traceback - -from ansible.errors import AnsibleError -from ansible.module_utils.basic import is_executable -from ansible.module_utils.common.process import get_bin_path -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes, to_native -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local chroot based connections ''' - - transport = 'chroot' - has_pipelining = True - # su currently has an undiagnosed issue with calculating the file - # checksums (so copy, for instance, doesn't work right) - # Have to look into that before re-enabling this - has_tty = False - - default_user = 'root' - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self.chroot = self._play_context.remote_addr - - if os.geteuid() != 0: - raise AnsibleError("chroot connection requires running as root") - - # we're running as root on the local system so do some - # trivial checks for ensuring 'host' is actually a chroot'able dir - if not os.path.isdir(self.chroot): - raise AnsibleError("%s is not a directory" % self.chroot) - - chrootsh = os.path.join(self.chroot, 'bin/sh') - # Want to check for a usable bourne shell inside the chroot. - # is_executable() == True is sufficient. For symlinks it - # gets really complicated really fast. So we punt on finding that - # out. As long as it's a symlink we assume that it will work - if not (is_executable(chrootsh) or (os.path.lexists(chrootsh) and os.path.islink(chrootsh))): - raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot) - - def _connect(self): - ''' connect to the chroot ''' - if os.path.isabs(self.get_option('chroot_exe')): - self.chroot_cmd = self.get_option('chroot_exe') - else: - try: - self.chroot_cmd = get_bin_path(self.get_option('chroot_exe')) - except ValueError as e: - raise AnsibleError(to_native(e)) - - super(Connection, self)._connect() - if not self._connected: - display.vvv("THIS IS A LOCAL CHROOT DIR", host=self.chroot) - self._connected = True - - def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE): - ''' run a command on the chroot. This is only needed for implementing - put_file() get_file() so that we don't have to read the whole file - into memory. - - compared to exec_command() it looses some niceties like being able to - return the process's exit code immediately. - ''' - executable = self.get_option('executable') - local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd] - - display.vvv("EXEC %s" % (local_cmd), host=self.chroot) - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - return p - - def exec_command(self, cmd, in_data=None, sudoable=False): - ''' run a command on the chroot ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - p = self._buffered_exec_command(cmd) - - stdout, stderr = p.communicate(in_data) - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to chroot ''' - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot) - - out_path = shlex_quote(self._prefix_login_path(out_path)) - try: - with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - try: - p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file) - except OSError: - raise AnsibleError("chroot connection requires dd command in the chroot") - try: - stdout, stderr = p.communicate() - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - except IOError: - raise AnsibleError("file or module does not exist at: %s" % in_path) - - def fetch_file(self, in_path, out_path): - ''' fetch a file from chroot to local ''' - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot) - - in_path = shlex_quote(self._prefix_login_path(in_path)) - try: - p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE)) - except OSError: - raise AnsibleError("chroot connection requires dd command in the chroot") - - with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file: - try: - chunk = p.stdout.read(BUFSIZE) - while chunk: - out_file.write(chunk) - chunk = p.stdout.read(BUFSIZE) - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - stdout, stderr = p.communicate() - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - def close(self): - ''' terminate the connection; nothing to do here ''' - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/docker.py b/lib/ansible/plugins/connection/docker.py deleted file mode 100644 index 380a84d877..0000000000 --- a/lib/ansible/plugins/connection/docker.py +++ /dev/null @@ -1,357 +0,0 @@ -# Based on the chroot connection plugin by Maykel Moya -# -# (c) 2014, Lorin Hochstein -# (c) 2015, Leendert Brouwer (https://github.com/objectified) -# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# Copyright (c) 2017 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 = """ - author: - - Lorin Hochestein - - Leendert Brouwer - connection: docker - short_description: Run tasks in docker containers - description: - - Run commands or put/fetch files to an existing docker container. - version_added: "2.0" - options: - remote_user: - description: - - The user to execute as inside the container - vars: - - name: ansible_user - - name: ansible_docker_user - docker_extra_args: - description: - - Extra arguments to pass to the docker command line - default: '' - remote_addr: - description: - - The name of the container you want to access. - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_docker_host -""" - -import distutils.spawn -import fcntl -import os -import os.path -import subprocess -import re - -from distutils.version import LooseVersion - -import ansible.constants as C -from ansible.compat import selectors -from ansible.errors import AnsibleError, AnsibleFileNotFound -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes, to_native, to_text -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local docker based connections ''' - - transport = 'docker' - has_pipelining = True - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - # Note: docker supports running as non-root in some configurations. - # (For instance, setting the UNIX socket file to be readable and - # writable by a specific UNIX group and then putting users into that - # group). Therefore we don't check that the user is root when using - # this connection. But if the user is getting a permission denied - # error it probably means that docker on their system is only - # configured to be connected to by root and they are not running as - # root. - - # Windows uses Powershell modules - if getattr(self._shell, "_IS_WINDOWS", False): - self.module_implementation_preferences = ('.ps1', '.exe', '') - - if 'docker_command' in kwargs: - self.docker_cmd = kwargs['docker_command'] - else: - self.docker_cmd = distutils.spawn.find_executable('docker') - if not self.docker_cmd: - raise AnsibleError("docker command not found in PATH") - - docker_version = self._get_docker_version() - if docker_version == u'dev': - display.warning(u'Docker version number is "dev". Will assume latest version.') - if docker_version != u'dev' and LooseVersion(docker_version) < LooseVersion(u'1.3'): - raise AnsibleError('docker connection type requires docker 1.3 or higher') - - # The remote user we will request from docker (if supported) - self.remote_user = None - # The actual user which will execute commands in docker (if known) - self.actual_user = None - - if self._play_context.remote_user is not None: - if docker_version == u'dev' or LooseVersion(docker_version) >= LooseVersion(u'1.7'): - # Support for specifying the exec user was added in docker 1.7 - self.remote_user = self._play_context.remote_user - self.actual_user = self.remote_user - else: - self.actual_user = self._get_docker_remote_user() - - if self.actual_user != self._play_context.remote_user: - display.warning(u'docker {0} does not support remote_user, using container default: {1}' - .format(docker_version, self.actual_user or u'?')) - elif self._display.verbosity > 2: - # Since we're not setting the actual_user, look it up so we have it for logging later - # Only do this if display verbosity is high enough that we'll need the value - # This saves overhead from calling into docker when we don't need to - self.actual_user = self._get_docker_remote_user() - - @staticmethod - def _sanitize_version(version): - return re.sub(u'[^0-9a-zA-Z.]', u'', version) - - def _old_docker_version(self): - cmd_args = [] - if self._play_context.docker_extra_args: - cmd_args += self._play_context.docker_extra_args.split(' ') - - old_version_subcommand = ['version'] - - old_docker_cmd = [self.docker_cmd] + cmd_args + old_version_subcommand - p = subprocess.Popen(old_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - cmd_output, err = p.communicate() - - return old_docker_cmd, to_native(cmd_output), err, p.returncode - - def _new_docker_version(self): - # no result yet, must be newer Docker version - cmd_args = [] - if self._play_context.docker_extra_args: - cmd_args += self._play_context.docker_extra_args.split(' ') - - new_version_subcommand = ['version', '--format', "'{{.Server.Version}}'"] - - new_docker_cmd = [self.docker_cmd] + cmd_args + new_version_subcommand - p = subprocess.Popen(new_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - cmd_output, err = p.communicate() - return new_docker_cmd, to_native(cmd_output), err, p.returncode - - def _get_docker_version(self): - - cmd, cmd_output, err, returncode = self._old_docker_version() - if returncode == 0: - for line in to_text(cmd_output, errors='surrogate_or_strict').split(u'\n'): - if line.startswith(u'Server version:'): # old docker versions - return self._sanitize_version(line.split()[2]) - - cmd, cmd_output, err, returncode = self._new_docker_version() - if returncode: - raise AnsibleError('Docker version check (%s) failed: %s' % (to_native(cmd), to_native(err))) - - return self._sanitize_version(to_text(cmd_output, errors='surrogate_or_strict')) - - def _get_docker_remote_user(self): - """ Get the default user configured in the docker container """ - p = subprocess.Popen([self.docker_cmd, 'inspect', '--format', '{{.Config.User}}', self._play_context.remote_addr], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - out, err = p.communicate() - out = to_text(out, errors='surrogate_or_strict') - - if p.returncode != 0: - display.warning(u'unable to retrieve default user from docker container: %s %s' % (out, to_text(err))) - return None - - # The default exec user is root, unless it was changed in the Dockerfile with USER - return out.strip() or u'root' - - def _build_exec_cmd(self, cmd): - """ Build the local docker exec command to run cmd on remote_host - - If remote_user is available and is supported by the docker - version we are using, it will be provided to docker exec. - """ - - local_cmd = [self.docker_cmd] - - if self._play_context.docker_extra_args: - local_cmd += self._play_context.docker_extra_args.split(' ') - - local_cmd += [b'exec'] - - if self.remote_user is not None: - local_cmd += [b'-u', self.remote_user] - - # -i is needed to keep stdin open which allows pipelining to work - local_cmd += [b'-i', self._play_context.remote_addr] + cmd - - return local_cmd - - def _connect(self, port=None): - """ Connect to the container. Nothing to do """ - super(Connection, self)._connect() - if not self._connected: - display.vvv(u"ESTABLISH DOCKER CONNECTION FOR USER: {0}".format( - self.actual_user or u'?'), host=self._play_context.remote_addr - ) - self._connected = True - - def exec_command(self, cmd, in_data=None, sudoable=False): - """ Run a command on the docker host """ - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd]) - - display.vvv(u"EXEC {0}".format(to_text(local_cmd)), host=self._play_context.remote_addr) - display.debug("opening command with Popen()") - - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - - p = subprocess.Popen( - local_cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - display.debug("done running command with Popen()") - - if self.become and self.become.expect_prompt() and sudoable: - fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) - fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK) - selector = selectors.DefaultSelector() - selector.register(p.stdout, selectors.EVENT_READ) - selector.register(p.stderr, selectors.EVENT_READ) - - become_output = b'' - try: - while not self.become.check_success(become_output) and not self.become.check_password_prompt(become_output): - events = selector.select(self._play_context.timeout) - if not events: - stdout, stderr = p.communicate() - raise AnsibleError('timeout waiting for privilege escalation password prompt:\n' + to_native(become_output)) - - for key, event in events: - if key.fileobj == p.stdout: - chunk = p.stdout.read() - elif key.fileobj == p.stderr: - chunk = p.stderr.read() - - if not chunk: - stdout, stderr = p.communicate() - raise AnsibleError('privilege output closed while waiting for password prompt:\n' + to_native(become_output)) - become_output += chunk - finally: - selector.close() - - if not self.become.check_success(become_output): - become_pass = self.become.get_option('become_pass', playcontext=self._play_context) - p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n') - fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) - fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK) - - display.debug("getting output with communicate()") - stdout, stderr = p.communicate(in_data) - display.debug("done communicating") - - display.debug("done with docker.exec_command()") - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - """ Transfer a file from local to docker container """ - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr) - - out_path = self._prefix_login_path(out_path) - if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')): - raise AnsibleFileNotFound( - "file or module does not exist: %s" % to_native(in_path)) - - out_path = shlex_quote(out_path) - # Older docker doesn't have native support for copying files into - # running containers, so we use docker exec to implement this - # Although docker version 1.8 and later provide support, the - # owner and group of the files are always set to root - with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)]) - args = [to_bytes(i, errors='surrogate_or_strict') for i in args] - try: - p = subprocess.Popen(args, stdin=in_file, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError: - raise AnsibleError("docker connection requires dd command in the container to put files") - stdout, stderr = p.communicate() - - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % - (to_native(in_path), to_native(out_path), to_native(stdout), to_native(stderr))) - - def fetch_file(self, in_path, out_path): - """ Fetch a file from container to local. """ - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr) - - in_path = self._prefix_login_path(in_path) - # out_path is the final file path, but docker takes a directory, not a - # file path - out_dir = os.path.dirname(out_path) - - args = [self.docker_cmd, "cp", "%s:%s" % (self._play_context.remote_addr, in_path), out_dir] - args = [to_bytes(i, errors='surrogate_or_strict') for i in args] - - p = subprocess.Popen(args, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - p.communicate() - - actual_out_path = os.path.join(out_dir, os.path.basename(in_path)) - - if p.returncode != 0: - # Older docker doesn't have native support for fetching files command `cp` - # If `cp` fails, try to use `dd` instead - args = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)]) - args = [to_bytes(i, errors='surrogate_or_strict') for i in args] - with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file: - try: - p = subprocess.Popen(args, stdin=subprocess.PIPE, - stdout=out_file, stderr=subprocess.PIPE) - except OSError: - raise AnsibleError("docker connection requires dd command in the container to put files") - stdout, stderr = p.communicate() - - if p.returncode != 0: - raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - # Rename if needed - if actual_out_path != out_path: - os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict')) - - def close(self): - """ Terminate the connection. Nothing to do for Docker""" - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/funcd.py b/lib/ansible/plugins/connection/funcd.py deleted file mode 100644 index 7bd957550e..0000000000 --- a/lib/ansible/plugins/connection/funcd.py +++ /dev/null @@ -1,103 +0,0 @@ -# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> -# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com> -# Copyright (c) 2013, Michael Scherer <misc@zarb.org> -# Copyright (c) 2017 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 = """ - author: Michael Scherer (@msherer) <misc@zarb.org> - connection: funcd - short_description: Use funcd to connect to target - description: - - This transport permits you to use Ansible over Func. - - For people who have already setup func and that wish to play with ansible, - this permit to move gradually to ansible without having to redo completely the setup of the network. - version_added: "1.1" - options: - remote_addr: - description: - - The path of the chroot you want to access. - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_func_host -""" - -HAVE_FUNC = False -try: - import func.overlord.client as fc - HAVE_FUNC = True -except ImportError: - pass - -import os -import tempfile -import shutil - -from ansible.errors import AnsibleError -from ansible.utils.display import Display - -display = Display() - - -class Connection(object): - ''' Func-based connections ''' - - has_pipelining = False - - def __init__(self, runner, host, port, *args, **kwargs): - self.runner = runner - self.host = host - # port is unused, this go on func - self.port = port - - def connect(self, port=None): - if not HAVE_FUNC: - raise AnsibleError("func is not installed") - - self.client = fc.Client(self.host) - return self - - def exec_command(self, cmd, become_user=None, sudoable=False, executable='/bin/sh', in_data=None): - ''' run a command on the remote minion ''' - - if in_data: - raise AnsibleError("Internal Error: this module does not support optimized module pipelining") - - # totally ignores privlege escalation - display.vvv("EXEC %s" % (cmd), host=self.host) - p = self.client.command.run(cmd)[self.host] - return (p[0], p[1], p[2]) - - def _normalize_path(self, path, prefix): - if not path.startswith(os.path.sep): - path = os.path.join(os.path.sep, path) - normpath = os.path.normpath(path) - return os.path.join(prefix, normpath[1:]) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to remote ''' - - out_path = self._normalize_path(out_path, '/') - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) - self.client.local.copyfile.send(in_path, out_path) - - def fetch_file(self, in_path, out_path): - ''' fetch a file from remote to local ''' - - in_path = self._normalize_path(in_path, '/') - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) - # need to use a tmp dir due to difference of semantic for getfile - # ( who take a # directory as destination) and fetch_file, who - # take a file directly - tmpdir = tempfile.mkdtemp(prefix="func_ansible") - self.client.local.getfile.get(in_path, tmpdir) - shutil.move(os.path.join(tmpdir, self.host, os.path.basename(in_path)), out_path) - shutil.rmtree(tmpdir) - - def close(self): - ''' terminate the connection; nothing to do here ''' - pass diff --git a/lib/ansible/plugins/connection/iocage.py b/lib/ansible/plugins/connection/iocage.py deleted file mode 100644 index a79a1702c0..0000000000 --- a/lib/ansible/plugins/connection/iocage.py +++ /dev/null @@ -1,83 +0,0 @@ -# Based on jail.py -# (c) 2013, Michael Scherer <misc@zarb.org> -# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# (c) 2016, Stephan Lohse <dev-github@ploek.org> -# Copyright (c) 2017 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 = """ - author: Stephan Lohse <dev-github@ploek.org> - connection: iocage - short_description: Run tasks in iocage jails - description: - - Run commands or put/fetch files to an existing iocage jail - version_added: "2.0" - options: - remote_addr: - description: - - Path to the jail - vars: - - name: ansible_host - - name: ansible_iocage_host - remote_user: - description: - - User to execute as inside the jail - vars: - - name: ansible_user - - name: ansible_iocage_user -""" - -import subprocess - -from ansible.plugins.connection.jail import Connection as Jail -from ansible.module_utils._text import to_native -from ansible.errors import AnsibleError -from ansible.utils.display import Display - -display = Display() - - -class Connection(Jail): - ''' Local iocage based connections ''' - - transport = 'iocage' - - def __init__(self, play_context, new_stdin, *args, **kwargs): - self.ioc_jail = play_context.remote_addr - - self.iocage_cmd = Jail._search_executable('iocage') - - jail_uuid = self.get_jail_uuid() - - kwargs[Jail.modified_jailname_key] = 'ioc-{0}'.format(jail_uuid) - - display.vvv(u"Jail {iocjail} has been translated to {rawjail}".format( - iocjail=self.ioc_jail, rawjail=kwargs[Jail.modified_jailname_key]), - host=kwargs[Jail.modified_jailname_key]) - - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - def get_jail_uuid(self): - p = subprocess.Popen([self.iocage_cmd, 'get', 'host_hostuuid', self.ioc_jail], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - - stdout, stderr = p.communicate() - - if stdout is not None: - stdout = to_native(stdout) - - if stderr is not None: - stderr = to_native(stderr) - - # otherwise p.returncode would not be set - p.wait() - - if p.returncode != 0: - raise AnsibleError(u"iocage returned an error: {0}".format(stdout)) - - return stdout.strip('\n') diff --git a/lib/ansible/plugins/connection/jail.py b/lib/ansible/plugins/connection/jail.py deleted file mode 100644 index 161817ba2d..0000000000 --- a/lib/ansible/plugins/connection/jail.py +++ /dev/null @@ -1,202 +0,0 @@ -# Based on local.py by Michael DeHaan <michael.dehaan@gmail.com> -# and chroot.py by Maykel Moya <mmoya@speedyrails.com> -# Copyright (c) 2013, Michael Scherer <misc@zarb.org> -# Copyright (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# Copyright (c) 2017 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 = """ - author: Ansible Core Team - connection: jail - short_description: Run tasks in jails - description: - - Run commands or put/fetch files to an existing jail - version_added: "2.0" - options: - remote_addr: - description: - - Path to the jail - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_jail_host - remote_user: - description: - - User to execute as inside the jail - vars: - - name: ansible_user - - name: ansible_jail_user -""" - -import distutils.spawn -import os -import os.path -import subprocess -import traceback -import ansible.constants as C - -from ansible.errors import AnsibleError -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes, to_native, to_text -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local BSD Jail based connections ''' - - modified_jailname_key = 'conn_jail_name' - - transport = 'jail' - # Pipelining may work. Someone needs to test by setting this to True and - # having pipelining=True in their ansible.cfg - has_pipelining = True - has_tty = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self.jail = self._play_context.remote_addr - if self.modified_jailname_key in kwargs: - self.jail = kwargs[self.modified_jailname_key] - - if os.geteuid() != 0: - raise AnsibleError("jail connection requires running as root") - - self.jls_cmd = self._search_executable('jls') - self.jexec_cmd = self._search_executable('jexec') - - if self.jail not in self.list_jails(): - raise AnsibleError("incorrect jail name %s" % self.jail) - - @staticmethod - def _search_executable(executable): - cmd = distutils.spawn.find_executable(executable) - if not cmd: - raise AnsibleError("%s command not found in PATH" % executable) - return cmd - - def list_jails(self): - p = subprocess.Popen([self.jls_cmd, '-q', 'name'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - stdout, stderr = p.communicate() - - return to_text(stdout, errors='surrogate_or_strict').split() - - def _connect(self): - ''' connect to the jail; nothing to do here ''' - super(Connection, self)._connect() - if not self._connected: - display.vvv(u"ESTABLISH JAIL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self.jail) - self._connected = True - - def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE): - ''' run a command on the jail. This is only needed for implementing - put_file() get_file() so that we don't have to read the whole file - into memory. - - compared to exec_command() it looses some niceties like being able to - return the process's exit code immediately. - ''' - - local_cmd = [self.jexec_cmd] - set_env = '' - - if self._play_context.remote_user is not None: - local_cmd += ['-U', self._play_context.remote_user] - # update HOME since -U does not update the jail environment - set_env = 'HOME=~' + self._play_context.remote_user + ' ' - - local_cmd += [self.jail, self._play_context.executable, '-c', set_env + cmd] - - display.vvv("EXEC %s" % (local_cmd,), host=self.jail) - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - return p - - def exec_command(self, cmd, in_data=None, sudoable=False): - ''' run a command on the jail ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - p = self._buffered_exec_command(cmd) - - stdout, stderr = p.communicate(in_data) - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to jail ''' - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.jail) - - out_path = shlex_quote(self._prefix_login_path(out_path)) - try: - with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - try: - p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file) - except OSError: - raise AnsibleError("jail connection requires dd command in the jail") - try: - stdout, stderr = p.communicate() - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr))) - except IOError: - raise AnsibleError("file or module does not exist at: %s" % in_path) - - def fetch_file(self, in_path, out_path): - ''' fetch a file from jail to local ''' - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.jail) - - in_path = shlex_quote(self._prefix_login_path(in_path)) - try: - p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE)) - except OSError: - raise AnsibleError("jail connection requires dd command in the jail") - - with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file: - try: - chunk = p.stdout.read(BUFSIZE) - while chunk: - out_file.write(chunk) - chunk = p.stdout.read(BUFSIZE) - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - stdout, stderr = p.communicate() - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr))) - - def close(self): - ''' terminate the connection; nothing to do here ''' - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/kubectl.py b/lib/ansible/plugins/connection/kubectl.py deleted file mode 100644 index d8a0fcf697..0000000000 --- a/lib/ansible/plugins/connection/kubectl.py +++ /dev/null @@ -1,356 +0,0 @@ -# Based on the docker connection plugin -# -# Connection plugin for configuring kubernetes containers with kubectl -# (c) 2017, XuXinkun <xuxinkun@gmail.com> -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see <http://www.gnu.org/licenses/>. -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ - author: - - xuxinkun - - connection: kubectl - - short_description: Execute tasks in pods running on Kubernetes. - - description: - - Use the kubectl exec command to run tasks in, or put/fetch files to, pods running on the Kubernetes - container platform. - - version_added: "2.5" - - requirements: - - kubectl (go binary) - - options: - kubectl_pod: - description: - - Pod name. Required when the host name does not match pod name. - default: '' - vars: - - name: ansible_kubectl_pod - env: - - name: K8S_AUTH_POD - kubectl_container: - description: - - Container name. Required when a pod contains more than one container. - default: '' - vars: - - name: ansible_kubectl_container - env: - - name: K8S_AUTH_CONTAINER - kubectl_namespace: - description: - - The namespace of the pod - default: '' - vars: - - name: ansible_kubectl_namespace - env: - - name: K8S_AUTH_NAMESPACE - kubectl_extra_args: - description: - - Extra arguments to pass to the kubectl command line. - default: '' - vars: - - name: ansible_kubectl_extra_args - env: - - name: K8S_AUTH_EXTRA_ARGS - kubectl_kubeconfig: - description: - - Path to a kubectl config file. Defaults to I(~/.kube/config) - default: '' - vars: - - name: ansible_kubectl_kubeconfig - - name: ansible_kubectl_config - env: - - name: K8S_AUTH_KUBECONFIG - kubectl_context: - description: - - The name of a context found in the K8s config file. - default: '' - vars: - - name: ansible_kubectl_context - env: - - name: k8S_AUTH_CONTEXT - kubectl_host: - description: - - URL for accessing the API. - default: '' - vars: - - name: ansible_kubectl_host - - name: ansible_kubectl_server - env: - - name: K8S_AUTH_HOST - - name: K8S_AUTH_SERVER - kubectl_username: - description: - - Provide a username for authenticating with the API. - default: '' - vars: - - name: ansible_kubectl_username - - name: ansible_kubectl_user - env: - - name: K8S_AUTH_USERNAME - kubectl_password: - description: - - Provide a password for authenticating with the API. - default: '' - vars: - - name: ansible_kubectl_password - env: - - name: K8S_AUTH_PASSWORD - kubectl_token: - description: - - API authentication bearer token. - vars: - - name: ansible_kubectl_token - - name: ansible_kubectl_api_key - env: - - name: K8S_AUTH_TOKEN - - name: K8S_AUTH_API_KEY - client_cert: - description: - - Path to a certificate used to authenticate with the API. - default: '' - vars: - - name: ansible_kubectl_cert_file - - name: ansible_kubectl_client_cert - env: - - name: K8S_AUTH_CERT_FILE - aliases: [ kubectl_cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. - default: '' - vars: - - name: ansible_kubectl_key_file - - name: ansible_kubectl_client_key - env: - - name: K8S_AUTH_KEY_FILE - aliases: [ kubectl_key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. - default: '' - vars: - - name: ansible_kubectl_ssl_ca_cert - - name: ansible_kubectl_ca_cert - env: - - name: K8S_AUTH_SSL_CA_CERT - aliases: [ kubectl_ssl_ca_cert ] - validate_certs: - description: - - Whether or not to verify the API server's SSL certificate. Defaults to I(true). - default: '' - vars: - - name: ansible_kubectl_verify_ssl - - name: ansible_kubectl_validate_certs - env: - - name: K8S_AUTH_VERIFY_SSL - aliases: [ kubectl_verify_ssl ] -""" - -import distutils.spawn -import os -import os.path -import subprocess - -import ansible.constants as C -from ansible.parsing.yaml.loader import AnsibleLoader -from ansible.errors import AnsibleError, AnsibleFileNotFound -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -CONNECTION_TRANSPORT = 'kubectl' - -CONNECTION_OPTIONS = { - 'kubectl_container': '-c', - 'kubectl_namespace': '-n', - 'kubectl_kubeconfig': '--kubeconfig', - 'kubectl_context': '--context', - 'kubectl_host': '--server', - 'kubectl_username': '--username', - 'kubectl_password': '--password', - 'client_cert': '--client-certificate', - 'client_key': '--client-key', - 'ca_cert': '--certificate-authority', - 'validate_certs': '--insecure-skip-tls-verify', - 'kubectl_token': '--token' -} - - -class Connection(ConnectionBase): - ''' Local kubectl based connections ''' - - transport = CONNECTION_TRANSPORT - connection_options = CONNECTION_OPTIONS - documentation = DOCUMENTATION - has_pipelining = True - transport_cmd = None - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - # Note: kubectl runs commands as the user that started the container. - # It is impossible to set the remote user for a kubectl connection. - cmd_arg = '{0}_command'.format(self.transport) - if cmd_arg in kwargs: - self.transport_cmd = kwargs[cmd_arg] - else: - self.transport_cmd = distutils.spawn.find_executable(self.transport) - if not self.transport_cmd: - raise AnsibleError("{0} command not found in PATH".format(self.transport)) - - def _build_exec_cmd(self, cmd): - """ Build the local kubectl exec command to run cmd on remote_host - """ - local_cmd = [self.transport_cmd] - - # Build command options based on doc string - doc_yaml = AnsibleLoader(self.documentation).get_single_data() - for key in doc_yaml.get('options'): - if key.endswith('verify_ssl') and self.get_option(key) != '': - # Translate verify_ssl to skip_verify_ssl, and output as string - skip_verify_ssl = not self.get_option(key) - local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower())) - elif not key.endswith('container') and self.get_option(key) and self.connection_options.get(key): - cmd_arg = self.connection_options[key] - local_cmd += [cmd_arg, self.get_option(key)] - - extra_args_name = u'{0}_extra_args'.format(self.transport) - if self.get_option(extra_args_name): - local_cmd += self.get_option(extra_args_name).split(' ') - - pod = self.get_option(u'{0}_pod'.format(self.transport)) - if not pod: - pod = self._play_context.remote_addr - # -i is needed to keep stdin open which allows pipelining to work - local_cmd += ['exec', '-i', pod] - - # if the pod has more than one container, then container is required - container_arg_name = u'{0}_container'.format(self.transport) - if self.get_option(container_arg_name): - local_cmd += ['-c', self.get_option(container_arg_name)] - - local_cmd += ['--'] + cmd - - return local_cmd - - def _connect(self, port=None): - """ Connect to the container. Nothing to do """ - super(Connection, self)._connect() - if not self._connected: - display.vvv(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._play_context.remote_addr) - self._connected = True - - def exec_command(self, cmd, in_data=None, sudoable=False): - """ Run a command in the container """ - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd]) - - display.vvv("EXEC %s" % (local_cmd,), host=self._play_context.remote_addr) - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - stdout, stderr = p.communicate(in_data) - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - """ Transfer a file from local to the container """ - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr) - - out_path = self._prefix_login_path(out_path) - if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')): - raise AnsibleFileNotFound( - "file or module does not exist: %s" % in_path) - - out_path = shlex_quote(out_path) - # kubectl doesn't have native support for copying files into - # running containers, so we use kubectl exec to implement this - with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)]) - args = [to_bytes(i, errors='surrogate_or_strict') for i in args] - try: - p = subprocess.Popen(args, stdin=in_file, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError: - raise AnsibleError("kubectl connection requires dd command in the container to put files") - stdout, stderr = p.communicate() - - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - def fetch_file(self, in_path, out_path): - """ Fetch a file from container to local. """ - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr) - - in_path = self._prefix_login_path(in_path) - out_dir = os.path.dirname(out_path) - - # kubectl doesn't have native support for fetching files from - # running containers, so we use kubectl exec to implement this - args = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)]) - args = [to_bytes(i, errors='surrogate_or_strict') for i in args] - actual_out_path = os.path.join(out_dir, os.path.basename(in_path)) - with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file: - try: - p = subprocess.Popen(args, stdin=subprocess.PIPE, - stdout=out_file, stderr=subprocess.PIPE) - except OSError: - raise AnsibleError( - "{0} connection requires dd command in the container to fetch files".format(self.transport) - ) - stdout, stderr = p.communicate() - - if p.returncode != 0: - raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - if actual_out_path != out_path: - os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict')) - - def close(self): - """ Terminate the connection. Nothing to do for kubectl""" - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/libvirt_lxc.py b/lib/ansible/plugins/connection/libvirt_lxc.py deleted file mode 100644 index 99525bcbfb..0000000000 --- a/lib/ansible/plugins/connection/libvirt_lxc.py +++ /dev/null @@ -1,182 +0,0 @@ -# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> -# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com> -# (c) 2013, Michael Scherer <misc@zarb.org> -# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# (c) 2017 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 = """ - author: Michael Scherer <misc@zarb.org> - connection: libvirt_lxc - short_description: Run tasks in lxc containers via libvirt - description: - - Run commands or put/fetch files to an existing lxc container using libvirt - version_added: "2.0" - options: - remote_addr: - description: - - Container identifier - default: The set user as per docker's configuration - vars: - - name: ansible_host - - name: ansible_libvirt_lxc_host -""" - -import distutils.spawn -import os -import os.path -import subprocess -import traceback - -from ansible import constants as C -from ansible.errors import AnsibleError -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local lxc based connections ''' - - transport = 'libvirt_lxc' - has_pipelining = True - # su currently has an undiagnosed issue with calculating the file - # checksums (so copy, for instance, doesn't work right) - # Have to look into that before re-enabling this - default_user = 'root' - has_tty = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - self.lxc = self._play_context.remote_addr - - self.virsh = self._search_executable('virsh') - - self._check_domain(self.lxc) - - def _search_executable(self, executable): - cmd = distutils.spawn.find_executable(executable) - if not cmd: - raise AnsibleError("%s command not found in PATH") % executable - return cmd - - def _check_domain(self, domain): - p = subprocess.Popen([self.virsh, '-q', '-c', 'lxc:///', 'dominfo', to_bytes(domain)], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - p.communicate() - if p.returncode: - raise AnsibleError("%s is not a lxc defined in libvirt" % domain) - - def _connect(self): - ''' connect to the lxc; nothing to do here ''' - super(Connection, self)._connect() - if not self._connected: - display.vvv("THIS IS A LOCAL LXC DIR", host=self.lxc) - self._connected = True - - def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE): - ''' run a command on the chroot. This is only needed for implementing - put_file() get_file() so that we don't have to read the whole file - into memory. - - compared to exec_command() it looses some niceties like being able to - return the process's exit code immediately. - ''' - executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else '/bin/sh' - local_cmd = [self.virsh, '-q', '-c', 'lxc:///', 'lxc-enter-namespace'] - - if C.DEFAULT_LIBVIRT_LXC_NOSECLABEL: - local_cmd += ['--noseclabel'] - - local_cmd += [self.lxc, '--', executable, '-c', cmd] - - display.vvv("EXEC %s" % (local_cmd,), host=self.lxc) - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - return p - - def exec_command(self, cmd, in_data=None, sudoable=False): - ''' run a command on the chroot ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - p = self._buffered_exec_command(cmd) - - stdout, stderr = p.communicate(in_data) - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to lxc ''' - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.lxc) - - out_path = shlex_quote(self._prefix_login_path(out_path)) - try: - with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - try: - p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file) - except OSError: - raise AnsibleError("chroot connection requires dd command in the chroot") - try: - stdout, stderr = p.communicate() - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - except IOError: - raise AnsibleError("file or module does not exist at: %s" % in_path) - - def fetch_file(self, in_path, out_path): - ''' fetch a file from lxc to local ''' - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.lxc) - - in_path = shlex_quote(self._prefix_login_path(in_path)) - try: - p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE)) - except OSError: - raise AnsibleError("chroot connection requires dd command in the chroot") - - with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file: - try: - chunk = p.stdout.read(BUFSIZE) - while chunk: - out_file.write(chunk) - chunk = p.stdout.read(BUFSIZE) - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - stdout, stderr = p.communicate() - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - def close(self): - ''' terminate the connection; nothing to do here ''' - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/lxc.py b/lib/ansible/plugins/connection/lxc.py deleted file mode 100644 index 42276a8eab..0000000000 --- a/lib/ansible/plugins/connection/lxc.py +++ /dev/null @@ -1,229 +0,0 @@ -# (c) 2015, Joerg Thalheim <joerg@higgsboson.tk> -# Copyright (c) 2017 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 = """ - author: Joerg Thalheim <joerg@higgsboson.tk> - connection: lxc - short_description: Run tasks in lxc containers via lxc python library - description: - - Run commands or put/fetch files to an existing lxc container using lxc python library - version_added: "2.0" - options: - remote_addr: - description: - - Container identifier - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_lxc_host - executable: - default: /bin/sh - description: - - Shell executable - vars: - - name: ansible_executable - - name: ansible_lxc_executable -""" - -import os -import shutil -import traceback -import select -import fcntl -import errno - -HAS_LIBLXC = False -try: - import lxc as _lxc - HAS_LIBLXC = True -except ImportError: - pass - -from ansible import constants as C -from ansible import errors -from ansible.module_utils._text import to_bytes, to_native -from ansible.plugins.connection import ConnectionBase - - -class Connection(ConnectionBase): - ''' Local lxc based connections ''' - - transport = 'lxc' - has_pipelining = True - default_user = 'root' - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self.container_name = self._play_context.remote_addr - self.container = None - - def _connect(self): - ''' connect to the lxc; nothing to do here ''' - super(Connection, self)._connect() - - if not HAS_LIBLXC: - msg = "lxc bindings for python2 are not installed" - raise errors.AnsibleError(msg) - - if self.container: - return - - self._display.vvv("THIS IS A LOCAL LXC DIR", host=self.container_name) - self.container = _lxc.Container(self.container_name) - if self.container.state == "STOPPED": - raise errors.AnsibleError("%s is not running" % self.container_name) - - def _communicate(self, pid, in_data, stdin, stdout, stderr): - buf = {stdout: [], stderr: []} - read_fds = [stdout, stderr] - if in_data: - write_fds = [stdin] - else: - write_fds = [] - while len(read_fds) > 0 or len(write_fds) > 0: - try: - ready_reads, ready_writes, _ = select.select(read_fds, write_fds, []) - except select.error as e: - if e.args[0] == errno.EINTR: - continue - raise - for fd in ready_writes: - in_data = in_data[os.write(fd, in_data):] - if len(in_data) == 0: - write_fds.remove(fd) - for fd in ready_reads: - data = os.read(fd, 32768) - if not data: - read_fds.remove(fd) - buf[fd].append(data) - - (pid, returncode) = os.waitpid(pid, 0) - - return returncode, b"".join(buf[stdout]), b"".join(buf[stderr]) - - def _set_nonblocking(self, fd): - flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK - fcntl.fcntl(fd, fcntl.F_SETFL, flags) - return fd - - def exec_command(self, cmd, in_data=None, sudoable=False): - ''' run a command on the chroot ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - # python2-lxc needs bytes. python3-lxc needs text. - executable = to_native(self._play_context.executable, errors='surrogate_or_strict') - local_cmd = [executable, '-c', to_native(cmd, errors='surrogate_or_strict')] - - read_stdout, write_stdout = None, None - read_stderr, write_stderr = None, None - read_stdin, write_stdin = None, None - - try: - read_stdout, write_stdout = os.pipe() - read_stderr, write_stderr = os.pipe() - - kwargs = { - 'stdout': self._set_nonblocking(write_stdout), - 'stderr': self._set_nonblocking(write_stderr), - 'env_policy': _lxc.LXC_ATTACH_CLEAR_ENV - } - - if in_data: - read_stdin, write_stdin = os.pipe() - kwargs['stdin'] = self._set_nonblocking(read_stdin) - - self._display.vvv("EXEC %s" % (local_cmd), host=self.container_name) - pid = self.container.attach(_lxc.attach_run_command, local_cmd, **kwargs) - if pid == -1: - msg = "failed to attach to container %s" % self.container_name - raise errors.AnsibleError(msg) - - write_stdout = os.close(write_stdout) - write_stderr = os.close(write_stderr) - if read_stdin: - read_stdin = os.close(read_stdin) - - return self._communicate(pid, - in_data, - write_stdin, - read_stdout, - read_stderr) - finally: - fds = [read_stdout, - write_stdout, - read_stderr, - write_stderr, - read_stdin, - write_stdin] - for fd in fds: - if fd: - os.close(fd) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to lxc ''' - super(Connection, self).put_file(in_path, out_path) - self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.container_name) - in_path = to_bytes(in_path, errors='surrogate_or_strict') - out_path = to_bytes(out_path, errors='surrogate_or_strict') - - if not os.path.exists(in_path): - msg = "file or module does not exist: %s" % in_path - raise errors.AnsibleFileNotFound(msg) - try: - src_file = open(in_path, "rb") - except IOError: - traceback.print_exc() - raise errors.AnsibleError("failed to open input file to %s" % in_path) - try: - def write_file(args): - with open(out_path, 'wb+') as dst_file: - shutil.copyfileobj(src_file, dst_file) - try: - self.container.attach_wait(write_file, None) - except IOError: - traceback.print_exc() - msg = "failed to transfer file to %s" % out_path - raise errors.AnsibleError(msg) - finally: - src_file.close() - - def fetch_file(self, in_path, out_path): - ''' fetch a file from lxc to local ''' - super(Connection, self).fetch_file(in_path, out_path) - self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.container_name) - in_path = to_bytes(in_path, errors='surrogate_or_strict') - out_path = to_bytes(out_path, errors='surrogate_or_strict') - - try: - dst_file = open(out_path, "wb") - except IOError: - traceback.print_exc() - msg = "failed to open output file %s" % out_path - raise errors.AnsibleError(msg) - try: - def write_file(args): - try: - with open(in_path, 'rb') as src_file: - shutil.copyfileobj(src_file, dst_file) - finally: - # this is needed in the lxc child process - # to flush internal python buffers - dst_file.close() - try: - self.container.attach_wait(write_file, None) - except IOError: - traceback.print_exc() - msg = "failed to transfer file from %s to %s" % (in_path, out_path) - raise errors.AnsibleError(msg) - finally: - dst_file.close() - - def close(self): - ''' terminate the connection; nothing to do here ''' - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/lxd.py b/lib/ansible/plugins/connection/lxd.py deleted file mode 100644 index 5326cd995c..0000000000 --- a/lib/ansible/plugins/connection/lxd.py +++ /dev/null @@ -1,126 +0,0 @@ -# (c) 2016 Matt Clay <matt@mystile.com> -# (c) 2017 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 = """ - author: Matt Clay <matt@mystile.com> - connection: lxd - short_description: Run tasks in lxc containers via lxc CLI - description: - - Run commands or put/fetch files to an existing lxc container using lxc CLI - version_added: "2.0" - options: - remote_addr: - description: - - Container identifier - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_lxd_host - executable: - description: - - shell to use for execution inside container - default: /bin/sh - vars: - - name: ansible_executable - - name: ansible_lxd_executable -""" - -import os -from distutils.spawn import find_executable -from subprocess import Popen, PIPE - -from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound -from ansible.module_utils._text import to_bytes, to_text -from ansible.plugins.connection import ConnectionBase - - -class Connection(ConnectionBase): - """ lxd based connections """ - - transport = "lxd" - has_pipelining = True - default_user = 'root' - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self._host = self._play_context.remote_addr - self._lxc_cmd = find_executable("lxc") - - if not self._lxc_cmd: - raise AnsibleError("lxc command not found in PATH") - - if self._play_context.remote_user is not None and self._play_context.remote_user != 'root': - self._display.warning('lxd does not support remote_user, using container default: root') - - def _connect(self): - """connect to lxd (nothing to do here) """ - super(Connection, self)._connect() - - if not self._connected: - self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self._host) - self._connected = True - - def exec_command(self, cmd, in_data=None, sudoable=True): - """ execute a command on the lxd host """ - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - self._display.vvv(u"EXEC {0}".format(cmd), host=self._host) - - local_cmd = [self._lxc_cmd, "exec", self._host, "--", self._play_context.executable, "-c", cmd] - - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru') - - process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) - stdout, stderr = process.communicate(in_data) - - stdout = to_text(stdout) - stderr = to_text(stderr) - - if stderr == "error: Container is not running.\n": - raise AnsibleConnectionFailure("container not running: %s" % self._host) - - if stderr == "error: not found\n": - raise AnsibleConnectionFailure("container not found: %s" % self._host) - - return process.returncode, stdout, stderr - - def put_file(self, in_path, out_path): - """ put a file from local to lxd """ - super(Connection, self).put_file(in_path, out_path) - - self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._host) - - if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')): - raise AnsibleFileNotFound("input path is not a file: %s" % in_path) - - local_cmd = [self._lxc_cmd, "file", "push", in_path, self._host + "/" + out_path] - - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - - process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) - process.communicate() - - def fetch_file(self, in_path, out_path): - """ fetch a file from lxd to local """ - super(Connection, self).fetch_file(in_path, out_path) - - self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host) - - local_cmd = [self._lxc_cmd, "file", "pull", self._host + "/" + in_path, out_path] - - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - - process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) - process.communicate() - - def close(self): - """ close the connection (nothing to do here) """ - super(Connection, self).close() - - self._connected = False diff --git a/lib/ansible/plugins/connection/oc.py b/lib/ansible/plugins/connection/oc.py deleted file mode 100644 index 7212e7bce3..0000000000 --- a/lib/ansible/plugins/connection/oc.py +++ /dev/null @@ -1,174 +0,0 @@ -# Based on the docker connection plugin -# -# Connection plugin for configuring kubernetes containers with kubectl -# (c) 2017, XuXinkun <xuxinkun@gmail.com> -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see <http://www.gnu.org/licenses/>. -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -DOCUMENTATION = """ - author: - - xuxinkun - - connection: oc - - short_description: Execute tasks in pods running on OpenShift. - - description: - - Use the oc exec command to run tasks in, or put/fetch files to, pods running on the OpenShift - container platform. - - version_added: "2.5" - - requirements: - - oc (go binary) - - options: - oc_pod: - description: - - Pod name. Required when the host name does not match pod name. - default: '' - vars: - - name: ansible_oc_pod - env: - - name: K8S_AUTH_POD - oc_container: - description: - - Container name. Required when a pod contains more than one container. - default: '' - vars: - - name: ansible_oc_container - env: - - name: K8S_AUTH_CONTAINER - oc_namespace: - description: - - The namespace of the pod - default: '' - vars: - - name: ansible_oc_namespace - env: - - name: K8S_AUTH_NAMESPACE - oc_extra_args: - description: - - Extra arguments to pass to the oc command line. - default: '' - vars: - - name: ansible_oc_extra_args - env: - - name: K8S_AUTH_EXTRA_ARGS - oc_kubeconfig: - description: - - Path to a oc config file. Defaults to I(~/.kube/conig) - default: '' - vars: - - name: ansible_oc_kubeconfig - - name: ansible_oc_config - env: - - name: K8S_AUTH_KUBECONFIG - oc_context: - description: - - The name of a context found in the K8s config file. - default: '' - vars: - - name: ansible_oc_context - env: - - name: k8S_AUTH_CONTEXT - oc_host: - description: - - URL for accessing the API. - default: '' - vars: - - name: ansible_oc_host - - name: ansible_oc_server - env: - - name: K8S_AUTH_HOST - - name: K8S_AUTH_SERVER - oc_token: - description: - - API authentication bearer token. - vars: - - name: ansible_oc_token - - name: ansible_oc_api_key - env: - - name: K8S_AUTH_TOKEN - - name: K8S_AUTH_API_KEY - client_cert: - description: - - Path to a certificate used to authenticate with the API. - default: '' - vars: - - name: ansible_oc_cert_file - - name: ansible_oc_client_cert - env: - - name: K8S_AUTH_CERT_FILE - aliases: [ oc_cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. - default: '' - vars: - - name: ansible_oc_key_file - - name: ansible_oc_client_key - env: - - name: K8S_AUTH_KEY_FILE - aliases: [ oc_key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. - default: '' - vars: - - name: ansible_oc_ssl_ca_cert - - name: ansible_oc_ca_cert - env: - - name: K8S_AUTH_SSL_CA_CERT - aliases: [ oc_ssl_ca_cert ] - validate_certs: - description: - - Whether or not to verify the API server's SSL certificate. Defaults to I(true). - default: '' - vars: - - name: ansible_oc_verify_ssl - - name: ansible_oc_validate_certs - env: - - name: K8S_AUTH_VERIFY_SSL - aliases: [ oc_verify_ssl ] -""" - -from ansible.plugins.connection.kubectl import Connection as KubectlConnection - - -CONNECTION_TRANSPORT = 'oc' - -CONNECTION_OPTIONS = { - 'oc_container': '-c', - 'oc_namespace': '-n', - 'oc_kubeconfig': '--config', - 'oc_context': '--context', - 'oc_host': '--server', - 'client_cert': '--client-certificate', - 'client_key': '--client-key', - 'ca_cert': '--certificate-authority', - 'validate_certs': '--insecure-skip-tls-verify', - 'oc_token': '--token' -} - - -class Connection(KubectlConnection): - ''' Local oc based connections ''' - transport = CONNECTION_TRANSPORT - connection_options = CONNECTION_OPTIONS - documentation = DOCUMENTATION diff --git a/lib/ansible/plugins/connection/qubes.py b/lib/ansible/plugins/connection/qubes.py deleted file mode 100644 index 58a9467765..0000000000 --- a/lib/ansible/plugins/connection/qubes.py +++ /dev/null @@ -1,160 +0,0 @@ -# Based on the buildah connection plugin -# Copyright (c) 2017 Ansible Project -# 2018 Kushal Das -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# -# -# Written by: Kushal Das (https://github.com/kushaldas) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -DOCUMENTATION = """ - connection: qubes - short_description: Interact with an existing QubesOS AppVM - - description: - - Run commands or put/fetch files to an existing Qubes AppVM using qubes tools. - - author: Kushal Das (@kushaldas) - - version_added: "2.8" - - options: - remote_addr: - description: - - vm name - default: inventory_hostname - vars: - - name: ansible_host - remote_user: - description: - - The user to execute as inside the vm. - default: The *user* account as default in Qubes OS. - vars: - - name: ansible_user -# keyword: -# - name: hosts -""" - -import shlex -import shutil - -import os -import base64 -import subprocess - -import ansible.constants as C -from ansible.module_utils._text import to_bytes, to_native -from ansible.plugins.connection import ConnectionBase, ensure_connect -from ansible.errors import AnsibleConnectionFailure -from ansible.utils.display import Display - -display = Display() - - -# this _has to be_ named Connection -class Connection(ConnectionBase): - """This is a connection plugin for qubes: it uses qubes-run-vm binary to interact with the containers.""" - - # String used to identify this Connection class from other classes - transport = 'qubes' - has_pipelining = True - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self._remote_vmname = self._play_context.remote_addr - self._connected = False - # Default username in Qubes - self.user = "user" - if self._play_context.remote_user: - self.user = self._play_context.remote_user - - def _qubes(self, cmd=None, in_data=None, shell="qubes.VMShell"): - """run qvm-run executable - - :param cmd: cmd string for remote system - :param in_data: data passed to qvm-run-vm's stdin - :return: return code, stdout, stderr - """ - display.vvvv("CMD: ", cmd) - if not cmd.endswith("\n"): - cmd = cmd + "\n" - local_cmd = [] - - # For dom0 - local_cmd.extend(["qvm-run", "--pass-io", "--service"]) - if self.user != "user": - # Means we have a remote_user value - local_cmd.extend(["-u", self.user]) - - local_cmd.append(self._remote_vmname) - - local_cmd.append(shell) - - local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - - display.vvvv("Local cmd: ", local_cmd) - - display.vvv("RUN %s" % (local_cmd,), host=self._remote_vmname) - p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - # Here we are writing the actual command to the remote bash - p.stdin.write(to_bytes(cmd, errors='surrogate_or_strict')) - stdout, stderr = p.communicate(input=in_data) - return p.returncode, stdout, stderr - - def _connect(self): - """No persistent connection is being maintained.""" - super(Connection, self)._connect() - self._connected = True - - @ensure_connect - def exec_command(self, cmd, in_data=None, sudoable=False): - """Run specified command in a running QubesVM """ - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - display.vvvv("CMD IS: %s" % cmd) - - rc, stdout, stderr = self._qubes(cmd) - - 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 VM at 'out_path' """ - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._remote_vmname) - - with open(in_path, "rb") as fobj: - source_data = fobj.read() - - retcode, dummy, dummy = self._qubes('cat > "{0}"\n'.format(out_path), source_data, "qubes.VMRootShell") - # if qubes.VMRootShell service not supported, fallback to qubes.VMShell and - # hope it will have appropriate permissions - if retcode == 127: - retcode, dummy, dummy = self._qubes('cat > "{0}"\n'.format(out_path), source_data) - - if retcode != 0: - raise AnsibleConnectionFailure('Failed to put_file to {0}'.format(out_path)) - - 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._remote_vmname) - - # We are running in dom0 - cmd_args_list = ["qvm-run", "--pass-io", self._remote_vmname, "cat {0}".format(in_path)] - with open(out_path, "wb") as fobj: - p = subprocess.Popen(cmd_args_list, shell=False, stdout=fobj) - p.communicate() - if p.returncode != 0: - raise AnsibleConnectionFailure('Failed to fetch file to {0}'.format(out_path)) - - def close(self): - """ Closing the connection """ - super(Connection, self).close() - self._connected = False diff --git a/lib/ansible/plugins/connection/saltstack.py b/lib/ansible/plugins/connection/saltstack.py deleted file mode 100644 index aacbf048f6..0000000000 --- a/lib/ansible/plugins/connection/saltstack.py +++ /dev/null @@ -1,106 +0,0 @@ -# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> -# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com> -# Based on func.py -# (c) 2014, Michael Scherer <misc@zarb.org> -# (c) 2017 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 = """ - author: Michael Scherer (@mscherer) <misc@zarb.org> - connection: saltstack - short_description: Allow ansible to piggyback on salt minions - description: - - This allows you to use existing Saltstack infrastructure to connect to targets. - version_added: "2.2" -""" - -import re -import os -import pty -import subprocess - -from ansible.module_utils._text import to_bytes, to_text -from ansible.module_utils.six.moves import cPickle - -HAVE_SALTSTACK = False -try: - import salt.client as sc - HAVE_SALTSTACK = True -except ImportError: - pass - -import os -from ansible import errors -from ansible.plugins.connection import ConnectionBase - - -class Connection(ConnectionBase): - ''' Salt-based connections ''' - - has_pipelining = False - # while the name of the product is salt, naming that module salt cause - # trouble with module import - transport = 'saltstack' - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - self.host = self._play_context.remote_addr - - def _connect(self): - if not HAVE_SALTSTACK: - raise errors.AnsibleError("saltstack is not installed") - - self.client = sc.LocalClient() - self._connected = True - return self - - def exec_command(self, cmd, sudoable=False, in_data=None): - ''' run a command on the remote minion ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - if in_data: - raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") - - self._display.vvv("EXEC %s" % (cmd), host=self.host) - # need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077 - res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd]) - if self.host not in res: - raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host) - - p = res[self.host] - return (p['retcode'], p['stdout'], p['stderr']) - - def _normalize_path(self, path, prefix): - if not path.startswith(os.path.sep): - path = os.path.join(os.path.sep, path) - normpath = os.path.normpath(path) - return os.path.join(prefix, normpath[1:]) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to remote ''' - - super(Connection, self).put_file(in_path, out_path) - - out_path = self._normalize_path(out_path, '/') - self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) - with open(in_path) as in_fh: - content = in_fh.read() - self.client.cmd(self.host, 'file.write', [out_path, content]) - - # TODO test it - def fetch_file(self, in_path, out_path): - ''' fetch a file from remote to local ''' - - super(Connection, self).fetch_file(in_path, out_path) - - in_path = self._normalize_path(in_path, '/') - self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) - content = self.client.cmd(self.host, 'cp.get_file_str', [in_path])[self.host] - open(out_path, 'wb').write(content) - - def close(self): - ''' terminate the connection; nothing to do here ''' - pass diff --git a/lib/ansible/plugins/connection/zone.py b/lib/ansible/plugins/connection/zone.py deleted file mode 100644 index 119d6e3afb..0000000000 --- a/lib/ansible/plugins/connection/zone.py +++ /dev/null @@ -1,201 +0,0 @@ -# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> -# and chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com> -# and jail.py (c) 2013, Michael Scherer <misc@zarb.org> -# (c) 2015, Dagobert Michelsen <dam@baltic-online.de> -# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com> -# Copyright (c) 2017 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 = """ - author: Ansible Core Team - connection: zone - short_description: Run tasks in a zone instance - description: - - Run commands or put/fetch files to an existing zone - version_added: "2.0" - options: - remote_addr: - description: - - Zone identifier - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_zone_host -""" - -import distutils.spawn -import os -import os.path -import subprocess -import traceback - -from ansible import constants as C -from ansible.errors import AnsibleError -from ansible.module_utils.six.moves import shlex_quote -from ansible.module_utils._text import to_bytes -from ansible.plugins.connection import ConnectionBase, BUFSIZE -from ansible.utils.display import Display - -display = Display() - - -class Connection(ConnectionBase): - ''' Local zone based connections ''' - - transport = 'zone' - has_pipelining = True - has_tty = False - - def __init__(self, play_context, new_stdin, *args, **kwargs): - super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) - - self.zone = self._play_context.remote_addr - - if os.geteuid() != 0: - raise AnsibleError("zone connection requires running as root") - - self.zoneadm_cmd = to_bytes(self._search_executable('zoneadm')) - self.zlogin_cmd = to_bytes(self._search_executable('zlogin')) - - if self.zone not in self.list_zones(): - raise AnsibleError("incorrect zone name %s" % self.zone) - - @staticmethod - def _search_executable(executable): - cmd = distutils.spawn.find_executable(executable) - if not cmd: - raise AnsibleError("%s command not found in PATH" % executable) - return cmd - - def list_zones(self): - process = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - zones = [] - for l in process.stdout.readlines(): - # 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared - s = l.split(':') - if s[1] != 'global': - zones.append(s[1]) - - return zones - - def get_zone_path(self): - # solaris10vm# zoneadm -z cswbuild list -p - # -:cswbuild:installed:/zones/cswbuild:479f3c4b-d0c6-e97b-cd04-fd58f2c0238e:native:shared - process = subprocess.Popen([self.zoneadm_cmd, '-z', to_bytes(self.zone), 'list', '-p'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - # stdout, stderr = p.communicate() - path = process.stdout.readlines()[0].split(':')[3] - return path + '/root' - - def _connect(self): - ''' connect to the zone; nothing to do here ''' - super(Connection, self)._connect() - if not self._connected: - display.vvv("THIS IS A LOCAL ZONE DIR", host=self.zone) - self._connected = True - - def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE): - ''' run a command on the zone. This is only needed for implementing - put_file() get_file() so that we don't have to read the whole file - into memory. - - compared to exec_command() it looses some niceties like being able to - return the process's exit code immediately. - ''' - # NOTE: zlogin invokes a shell (just like ssh does) so we do not pass - # this through /bin/sh -c here. Instead it goes through the shell - # that zlogin selects. - local_cmd = [self.zlogin_cmd, self.zone, cmd] - local_cmd = map(to_bytes, local_cmd) - - display.vvv("EXEC %s" % (local_cmd), host=self.zone) - p = subprocess.Popen(local_cmd, shell=False, stdin=stdin, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - return p - - def exec_command(self, cmd, in_data=None, sudoable=False): - ''' run a command on the zone ''' - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - - p = self._buffered_exec_command(cmd) - - stdout, stderr = p.communicate(in_data) - return (p.returncode, stdout, stderr) - - def _prefix_login_path(self, remote_path): - ''' Make sure that we put files into a standard path - - If a path is relative, then we need to choose where to put it. - ssh chooses $HOME but we aren't guaranteed that a home dir will - exist in any given chroot. So for now we're choosing "/" instead. - This also happens to be the former default. - - Can revisit using $HOME instead if it's a problem - ''' - if not remote_path.startswith(os.path.sep): - remote_path = os.path.join(os.path.sep, remote_path) - return os.path.normpath(remote_path) - - def put_file(self, in_path, out_path): - ''' transfer a file from local to zone ''' - super(Connection, self).put_file(in_path, out_path) - display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.zone) - - out_path = shlex_quote(self._prefix_login_path(out_path)) - try: - with open(in_path, 'rb') as in_file: - if not os.fstat(in_file.fileno()).st_size: - count = ' count=0' - else: - count = '' - try: - p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file) - except OSError: - raise AnsibleError("jail connection requires dd command in the jail") - try: - stdout, stderr = p.communicate() - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - except IOError: - raise AnsibleError("file or module does not exist at: %s" % in_path) - - def fetch_file(self, in_path, out_path): - ''' fetch a file from zone to local ''' - super(Connection, self).fetch_file(in_path, out_path) - display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.zone) - - in_path = shlex_quote(self._prefix_login_path(in_path)) - try: - p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE)) - except OSError: - raise AnsibleError("zone connection requires dd command in the zone") - - with open(out_path, 'wb+') as out_file: - try: - chunk = p.stdout.read(BUFSIZE) - while chunk: - out_file.write(chunk) - chunk = p.stdout.read(BUFSIZE) - except Exception: - traceback.print_exc() - raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path)) - stdout, stderr = p.communicate() - if p.returncode != 0: - raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr)) - - def close(self): - ''' terminate the connection; nothing to do here ''' - super(Connection, self).close() - self._connected = False |