mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-02 06:30:19 -07:00
forwarded docker_extra_args to latest upstream/origin/devel
This commit is contained in:
commit
cd2c140f69
421 changed files with 22824 additions and 4800 deletions
|
@ -1,4 +1,4 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
|
||||
# (c) 2015 Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
|
@ -23,6 +23,7 @@ __metaclass__ = type
|
|||
import fcntl
|
||||
import gettext
|
||||
import os
|
||||
import shlex
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
|
||||
from functools import wraps
|
||||
|
@ -31,6 +32,7 @@ from ansible.compat.six import with_metaclass
|
|||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins import shell_loader
|
||||
from ansible.utils.unicode import to_bytes, to_unicode
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -40,6 +42,8 @@ except ImportError:
|
|||
|
||||
__all__ = ['ConnectionBase', 'ensure_connect']
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
def ensure_connect(func):
|
||||
@wraps(func)
|
||||
|
@ -60,6 +64,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
# as discovered by the specified file extension. An empty string as the
|
||||
# language means any language.
|
||||
module_implementation_preferences = ('',)
|
||||
allow_executable = True
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
# All these hasattrs allow subclasses to override these parameters
|
||||
|
@ -83,7 +88,12 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
elif hasattr(self, '_shell_type'):
|
||||
shell_type = getattr(self, '_shell_type')
|
||||
else:
|
||||
shell_type = os.path.basename(C.DEFAULT_EXECUTABLE)
|
||||
shell_type = 'sh'
|
||||
shell_filename = os.path.basename(self._play_context.executable)
|
||||
for shell in shell_loader.all():
|
||||
if shell_filename in shell.COMPATIBLE_SHELLS:
|
||||
shell_type = shell.SHELL_FAMILY
|
||||
break
|
||||
|
||||
self._shell = shell_loader.get(shell_type)
|
||||
if not self._shell:
|
||||
|
@ -91,6 +101,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
|
||||
@property
|
||||
def connected(self):
|
||||
'''Read-only property holding whether the connection to the remote host is active or closed.'''
|
||||
return self._connected
|
||||
|
||||
def _become_method_supported(self):
|
||||
|
@ -112,6 +123,24 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
'''
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _split_ssh_args(argstring):
|
||||
"""
|
||||
Takes a string like '-o Foo=1 -o Bar="foo bar"' and returns a
|
||||
list ['-o', 'Foo=1', '-o', 'Bar=foo bar'] that can be added to
|
||||
the argument list. The list will not contain any empty elements.
|
||||
"""
|
||||
try:
|
||||
# Python 2.6.x shlex doesn't handle unicode type so we have to
|
||||
# convert args to byte string for that case. More efficient to
|
||||
# try without conversion first but python2.6 doesn't throw an
|
||||
# exception, it merely mangles the output:
|
||||
# >>> shlex.split(u't e')
|
||||
# ['t\x00\x00\x00', '\x00\x00\x00e\x00\x00\x00']
|
||||
return [to_unicode(x.strip()) for x in shlex.split(to_bytes(argstring)) if x.strip()]
|
||||
except AttributeError:
|
||||
return [to_unicode(x.strip()) for x in shlex.split(argstring) if x.strip()]
|
||||
|
||||
@abstractproperty
|
||||
def transport(self):
|
||||
"""String used to identify this Connection class from other classes"""
|
||||
|
@ -144,8 +173,8 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
When a command is executed, it goes through multiple commands to get
|
||||
there. It looks approximately like this::
|
||||
|
||||
HardCodedShell ConnectionCommand UsersLoginShell DEFAULT_EXECUTABLE BecomeCommand DEFAULT_EXECUTABLE Command
|
||||
:HardCodedShell: Is optional. It is run locally to invoke the
|
||||
[LocalShell] ConnectionCommand [UsersLoginShell (*)] ANSIBLE_SHELL_EXECUTABLE [(BecomeCommand ANSIBLE_SHELL_EXECUTABLE)] Command
|
||||
:LocalShell: Is optional. It is run locally to invoke the
|
||||
``Connection Command``. In most instances, the
|
||||
``ConnectionCommand`` can be invoked directly instead. The ssh
|
||||
connection plugin which can have values that need expanding
|
||||
|
@ -158,30 +187,36 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
``ansible_ssh_host`` and so forth are fed to this piece of the
|
||||
command to connect to the correct host (Examples ``ssh``,
|
||||
``chroot``)
|
||||
:UsersLoginShell: This is the shell that the ``ansible_ssh_user`` has
|
||||
configured as their login shell. In traditional UNIX parlance,
|
||||
this is the last field of a user's ``/etc/passwd`` entry We do not
|
||||
specifically try to run the ``UsersLoginShell`` when we connect.
|
||||
Instead it is implicit in the actions that the
|
||||
``ConnectionCommand`` takes when it connects to a remote machine.
|
||||
``ansible_shell_type`` may be set to inform ansible of differences
|
||||
in how the ``UsersLoginShell`` handles things like quoting if a
|
||||
shell has different semantics than the Bourne shell.
|
||||
:DEFAULT_EXECUTABLE: This is the shell accessible via
|
||||
``ansible.constants.DEFAULT_EXECUTABLE``. We explicitly invoke
|
||||
this shell so that we have predictable quoting rules at this
|
||||
point. The ``DEFAULT_EXECUTABLE`` is only settable by the user
|
||||
because some sudo setups may only allow invoking a specific Bourne
|
||||
shell. (For instance, ``/bin/bash`` may be allowed but
|
||||
``/bin/sh``, our default, may not). We invoke this twice, once
|
||||
after the ``ConnectionCommand`` and once after the
|
||||
:UsersLoginShell: This shell may or may not be created depending on
|
||||
the ConnectionCommand used by the connection plugin. This is the
|
||||
shell that the ``ansible_ssh_user`` has configured as their login
|
||||
shell. In traditional UNIX parlance, this is the last field of
|
||||
a user's ``/etc/passwd`` entry We do not specifically try to run
|
||||
the ``UsersLoginShell`` when we connect. Instead it is implicit
|
||||
in the actions that the ``ConnectionCommand`` takes when it
|
||||
connects to a remote machine. ``ansible_shell_type`` may be set
|
||||
to inform ansible of differences in how the ``UsersLoginShell``
|
||||
handles things like quoting if a shell has different semantics
|
||||
than the Bourne shell.
|
||||
:ANSIBLE_SHELL_EXECUTABLE: This is the shell set via the inventory var
|
||||
``ansible_shell_executable`` or via
|
||||
``constants.DEFAULT_EXECUTABLE`` if the inventory var is not set.
|
||||
We explicitly invoke this shell so that we have predictable
|
||||
quoting rules at this point. ``ANSIBLE_SHELL_EXECUTABLE`` is only
|
||||
settable by the user because some sudo setups may only allow
|
||||
invoking a specific shell. (For instance, ``/bin/bash`` may be
|
||||
allowed but ``/bin/sh``, our default, may not). We invoke this
|
||||
twice, once after the ``ConnectionCommand`` and once after the
|
||||
``BecomeCommand``. After the ConnectionCommand, this is run by
|
||||
the ``UsersLoginShell``. After the ``BecomeCommand`` we specify
|
||||
that the ``DEFAULT_EXECUTABLE`` is being invoked directly.
|
||||
:BecomeComand: Is the command that performs privilege escalation.
|
||||
Setting this up is performed by the action plugin prior to running
|
||||
``exec_command``. So we just get passed :param:`cmd` which has the
|
||||
BecomeCommand already added. (Examples: sudo, su)
|
||||
that the ``ANSIBLE_SHELL_EXECUTABLE`` is being invoked directly.
|
||||
:BecomeComand ANSIBLE_SHELL_EXECUTABLE: Is the command that performs
|
||||
privilege escalation. Setting this up is performed by the action
|
||||
plugin prior to running ``exec_command``. So we just get passed
|
||||
:param:`cmd` which has the BecomeCommand already added.
|
||||
(Examples: sudo, su) If we have a BecomeCommand then we will
|
||||
invoke a ANSIBLE_SHELL_EXECUTABLE shell inside of it so that we
|
||||
have a consistent view of quoting.
|
||||
:Command: Is the command we're actually trying to run remotely.
|
||||
(Examples: mkdir -p $HOME/.ansible, python $HOME/.ansible/tmp-script-file)
|
||||
"""
|
||||
|
@ -205,7 +240,10 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
pass
|
||||
|
||||
def check_become_success(self, output):
|
||||
return self._play_context.success_key == output.rstrip()
|
||||
for line in output.splitlines(True):
|
||||
if self._play_context.success_key == line.rstrip():
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_password_prompt(self, output):
|
||||
if self._play_context.prompt is None:
|
||||
|
|
|
@ -28,8 +28,9 @@ import traceback
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.module_utils.basic import is_executable
|
||||
from ansible.utils.unicode import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -37,8 +38,6 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local chroot based connections '''
|
||||
|
@ -90,6 +89,7 @@ class Connection(ConnectionBase):
|
|||
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='strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -125,7 +125,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
out_path = pipes.quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
with open(in_path, 'rb') as in_file:
|
||||
with open(to_bytes(in_path, errors='strict'), 'rb') as in_file:
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s' % (out_path, BUFSIZE), stdin=in_file)
|
||||
except OSError:
|
||||
|
@ -151,7 +151,7 @@ class Connection(ConnectionBase):
|
|||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
|
||||
with open(out_path, 'wb+') as out_file:
|
||||
with open(to_bytes(out_path, errors='strict'), 'wb+') as out_file:
|
||||
try:
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
while chunk:
|
||||
|
|
|
@ -35,7 +35,8 @@ from distutils.version import LooseVersion
|
|||
|
||||
import ansible.constants as C
|
||||
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.unicode import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -43,8 +44,6 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local docker based connections '''
|
||||
|
@ -75,18 +74,31 @@ class Connection(ConnectionBase):
|
|||
if not self.docker_cmd[0]:
|
||||
raise AnsibleError("docker command not found in PATH")
|
||||
|
||||
if play_context.connection_args:
|
||||
self.docker_cmd = self.docker_cmd + play_context.connection_args.split(' ')
|
||||
|
||||
self.can_copy_bothways = False
|
||||
|
||||
docker_version = self._get_docker_version()
|
||||
if LooseVersion(docker_version) < LooseVersion('1.3'):
|
||||
raise AnsibleError('docker connection type requires docker 1.3 or higher')
|
||||
# Docker cp in 1.8.0 sets the owner and group to root rather than the
|
||||
# user that the docker container is set to use by default.
|
||||
#if LooseVersion(docker_version) >= LooseVersion('1.8.0'):
|
||||
# self.can_copy_bothways = True
|
||||
|
||||
# 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 LooseVersion(docker_version) >= LooseVersion('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('docker {0} does not support remote_user, using container default: {1}'
|
||||
.format(docker_version, self.actual_user or '?'))
|
||||
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):
|
||||
|
@ -108,12 +120,48 @@ class Connection(ConnectionBase):
|
|||
|
||||
return self._sanitize_version(cmd_output)
|
||||
|
||||
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()
|
||||
|
||||
if p.returncode != 0:
|
||||
display.warning('unable to retrieve default user from docker container: %s' % out + err)
|
||||
return None
|
||||
|
||||
# The default exec user is root, unless it was changed in the Dockerfile with USER
|
||||
return out.strip() or '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 += ['exec']
|
||||
|
||||
if self.remote_user is not None:
|
||||
local_cmd += ['-u', self.remote_user]
|
||||
|
||||
# -i is needed to keep stdin open which allows pipelining to work
|
||||
local_cmd += ['-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("ESTABLISH DOCKER CONNECTION FOR USER: {0}".format(
|
||||
self._play_context.remote_user, host=self._play_context.remote_addr)
|
||||
display.vvv(u"ESTABLISH DOCKER CONNECTION FOR USER: {0}".format(
|
||||
self.actual_user or '?', host=self._play_context.remote_addr)
|
||||
)
|
||||
self._connected = True
|
||||
|
||||
|
@ -121,11 +169,10 @@ class Connection(ConnectionBase):
|
|||
""" Run a command on the docker host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else '/bin/sh'
|
||||
# -i is needed to keep stdin open which allows pipelining to work
|
||||
local_cmd = self.docker_cmd + ["exec", '-i', self._play_context.remote_addr, executable, '-c', cmd]
|
||||
local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self._play_context.remote_addr)
|
||||
display.vvv("EXEC %s" % (local_cmd,), host=self._play_context.remote_addr)
|
||||
local_cmd = [to_bytes(i, errors='strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -152,34 +199,27 @@ class Connection(ConnectionBase):
|
|||
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(in_path):
|
||||
if not os.path.exists(to_bytes(in_path, errors='strict')):
|
||||
raise AnsibleFileNotFound(
|
||||
"file or module does not exist: %s" % in_path)
|
||||
|
||||
if self.can_copy_bothways:
|
||||
# only docker >= 1.8.1 can do this natively
|
||||
args = self.docker_cmd + ["cp", in_path, "%s:%s" % (self._play_context.remote_addr, out_path) ]
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out_path = pipes.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
|
||||
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s" % (out_path, BUFSIZE)])
|
||||
args = [to_bytes(i, errors='strict') for i in args]
|
||||
with open(to_bytes(in_path, errors='strict'), 'rb') as in_file:
|
||||
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" % (in_path, out_path, stdout, stderr))
|
||||
else:
|
||||
out_path = pipes.quote(out_path)
|
||||
# Older docker doesn't have native support for copying files into
|
||||
# running containers, so we use docker exec to implement this
|
||||
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else '/bin/sh'
|
||||
args = self.docker_cmd + ["exec", "-i", self._play_context.remote_addr, executable, "-c",
|
||||
"dd of={0} bs={1}".format(out_path, BUFSIZE)]
|
||||
with open(in_path, 'rb') as in_file:
|
||||
try:
|
||||
p = subprocess.Popen(args, stdin=in_file,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError:
|
||||
raise AnsibleError("docker connection with docker < 1.8.1 requires dd command in the chroot")
|
||||
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. """
|
||||
|
@ -191,7 +231,8 @@ class Connection(ConnectionBase):
|
|||
# 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 = [self.docker_cmd, "cp", "%s:%s" % (self._play_context.remote_addr, in_path), out_dir]
|
||||
args = [to_bytes(i, errors='strict') for i in args]
|
||||
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
@ -200,7 +241,7 @@ class Connection(ConnectionBase):
|
|||
# Rename if needed
|
||||
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
|
||||
if actual_out_path != out_path:
|
||||
os.rename(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"""
|
||||
|
|
|
@ -29,7 +29,8 @@ import traceback
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.unicode import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -37,8 +38,6 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local BSD Jail based connections '''
|
||||
|
@ -46,7 +45,7 @@ class Connection(ConnectionBase):
|
|||
transport = 'jail'
|
||||
# Pipelining may work. Someone needs to test by setting this to True and
|
||||
# having pipelining=True in their ansible.cfg
|
||||
has_pipelining = False
|
||||
has_pipelining = True
|
||||
# Some become_methods may work in v2 (sudo works for other chroot-based
|
||||
# plugins while su seems to be failing). If some work, check chroot.py to
|
||||
# see how to disable just some methods.
|
||||
|
@ -70,7 +69,7 @@ class Connection(ConnectionBase):
|
|||
def _search_executable(executable):
|
||||
cmd = distutils.spawn.find_executable(executable)
|
||||
if not cmd:
|
||||
raise AnsibleError("%s command not found in PATH") % executable
|
||||
raise AnsibleError("%s command not found in PATH" % executable)
|
||||
return cmd
|
||||
|
||||
def list_jails(self):
|
||||
|
@ -83,7 +82,7 @@ class Connection(ConnectionBase):
|
|||
return stdout.split()
|
||||
|
||||
def get_jail_path(self):
|
||||
p = subprocess.Popen([self.jls_cmd, '-j', self.jail, '-q', 'path'],
|
||||
p = subprocess.Popen([self.jls_cmd, '-j', to_bytes(self.jail), '-q', 'path'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -109,7 +108,8 @@ class Connection(ConnectionBase):
|
|||
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else '/bin/sh'
|
||||
local_cmd = [self.jexec_cmd, self.jail, executable, '-c', cmd]
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self.jail)
|
||||
display.vvv("EXEC %s" % (local_cmd,), host=self.jail)
|
||||
local_cmd = [to_bytes(i, errors='strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -119,13 +119,6 @@ class Connection(ConnectionBase):
|
|||
''' run a command on the jail '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
# TODO: Check whether we can send the command to stdin via
|
||||
# p.communicate(in_data)
|
||||
# If we can, then we can change this plugin to has_pipelining=True and
|
||||
# remove the error if in_data is given.
|
||||
if in_data:
|
||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
|
@ -152,7 +145,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
out_path = pipes.quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
with open(in_path, 'rb') as in_file:
|
||||
with open(to_bytes(in_path, errors='strict'), 'rb') as in_file:
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s' % (out_path, BUFSIZE), stdin=in_file)
|
||||
except OSError:
|
||||
|
@ -178,7 +171,7 @@ class Connection(ConnectionBase):
|
|||
except OSError:
|
||||
raise AnsibleError("jail connection requires dd command in the jail")
|
||||
|
||||
with open(out_path, 'wb+') as out_file:
|
||||
with open(to_bytes(out_path, errors='strict'), 'wb+') as out_file:
|
||||
try:
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
while chunk:
|
||||
|
|
|
@ -29,7 +29,8 @@ import traceback
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.unicode import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -37,8 +38,6 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local lxc based connections '''
|
||||
|
@ -65,7 +64,7 @@ class Connection(ConnectionBase):
|
|||
return cmd
|
||||
|
||||
def _check_domain(self, domain):
|
||||
p = subprocess.Popen([self.virsh, '-q', '-c', 'lxc:///', 'dominfo', domain],
|
||||
p = subprocess.Popen([self.virsh, '-q', '-c', 'lxc:///', 'dominfo', to_bytes(domain)],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p.communicate()
|
||||
if p.returncode:
|
||||
|
@ -87,9 +86,15 @@ class Connection(ConnectionBase):
|
|||
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', self.lxc, '--', executable , '-c', cmd]
|
||||
local_cmd = [self.virsh, '-q', '-c', 'lxc:///', 'lxc-enter-namespace']
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self.lxc)
|
||||
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='strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -125,7 +130,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
out_path = pipes.quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
with open(in_path, 'rb') as in_file:
|
||||
with open(to_bytes(in_path, errors='strict'), 'rb') as in_file:
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s' % (out_path, BUFSIZE), stdin=in_file)
|
||||
except OSError:
|
||||
|
@ -151,7 +156,7 @@ class Connection(ConnectionBase):
|
|||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
|
||||
with open(out_path, 'wb+') as out_file:
|
||||
with open(to_bytes(out_path, errors='strict'), 'wb+') as out_file:
|
||||
try:
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
while chunk:
|
||||
|
|
|
@ -19,16 +19,19 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import select
|
||||
import shutil
|
||||
import subprocess
|
||||
import select
|
||||
import fcntl
|
||||
import getpass
|
||||
|
||||
from ansible.compat.six import text_type, binary_type
|
||||
|
||||
import ansible.constants as C
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.unicode import to_bytes, to_str
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -40,10 +43,8 @@ except ImportError:
|
|||
class Connection(ConnectionBase):
|
||||
''' Local based connections '''
|
||||
|
||||
@property
|
||||
def transport(self):
|
||||
''' used to identify this connection object '''
|
||||
return 'local'
|
||||
transport = 'local'
|
||||
has_pipelining = True
|
||||
|
||||
def _connect(self):
|
||||
''' connect to the local host; nothing to do here '''
|
||||
|
@ -54,7 +55,7 @@ class Connection(ConnectionBase):
|
|||
self._play_context.remote_user = getpass.getuser()
|
||||
|
||||
if not self._connected:
|
||||
display.vvv("ESTABLISH LOCAL CONNECTION FOR USER: {0}".format(self._play_context.remote_user, host=self._play_context.remote_addr))
|
||||
display.vvv(u"ESTABLISH LOCAL CONNECTION FOR USER: {0}".format(self._play_context.remote_user, host=self._play_context.remote_addr))
|
||||
self._connected = True
|
||||
return self
|
||||
|
||||
|
@ -65,16 +66,20 @@ class Connection(ConnectionBase):
|
|||
|
||||
display.debug("in local.exec_command()")
|
||||
|
||||
if in_data:
|
||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else None
|
||||
|
||||
display.vvv("{0} EXEC {1}".format(self._play_context.remote_addr, cmd))
|
||||
display.vvv(u"{0} EXEC {1}".format(self._play_context.remote_addr, cmd))
|
||||
# FIXME: cwd= needs to be set to the basedir of the playbook
|
||||
display.debug("opening command with Popen()")
|
||||
|
||||
if isinstance(cmd, (text_type, binary_type)):
|
||||
cmd = to_bytes(cmd)
|
||||
else:
|
||||
cmd = map(to_bytes, cmd)
|
||||
|
||||
p = subprocess.Popen(
|
||||
cmd,
|
||||
shell=isinstance(cmd, basestring),
|
||||
shell=isinstance(cmd, (text_type, binary_type)),
|
||||
executable=executable, #cwd=...
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
|
@ -106,7 +111,7 @@ class Connection(ConnectionBase):
|
|||
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()
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
display.debug("done communicating")
|
||||
|
||||
display.debug("done with local.exec_command()")
|
||||
|
@ -117,22 +122,22 @@ class Connection(ConnectionBase):
|
|||
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
display.vvv("{0} PUT {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
|
||||
if not os.path.exists(in_path):
|
||||
raise AnsibleFileNotFound("file or module does not exist: {0}".format(in_path))
|
||||
display.vvv(u"{0} PUT {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
|
||||
if not os.path.exists(to_bytes(in_path, errors='strict')):
|
||||
raise AnsibleFileNotFound("file or module does not exist: {0}".format(to_str(in_path)))
|
||||
try:
|
||||
shutil.copyfile(in_path, out_path)
|
||||
shutil.copyfile(to_bytes(in_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
||||
except shutil.Error:
|
||||
raise AnsibleError("failed to copy: {0} and {1} are the same".format(in_path, out_path))
|
||||
raise AnsibleError("failed to copy: {0} and {1} are the same".format(to_str(in_path), to_str(out_path)))
|
||||
except IOError as e:
|
||||
raise AnsibleError("failed to transfer file to {0}: {1}".format(out_path, e))
|
||||
raise AnsibleError("failed to transfer file to {0}: {1}".format(to_str(out_path), to_str(e)))
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
''' fetch a file from local to local -- for copatibility '''
|
||||
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
display.vvv("{0} FETCH {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
|
||||
display.vvv(u"{0} FETCH {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
|
||||
self.put_file(in_path, out_path)
|
||||
|
||||
def close(self):
|
||||
|
|
|
@ -32,6 +32,7 @@ import tempfile
|
|||
import traceback
|
||||
import fcntl
|
||||
import sys
|
||||
import re
|
||||
|
||||
from termios import tcflush, TCIFLUSH
|
||||
from binascii import hexlify
|
||||
|
@ -42,6 +43,7 @@ from ansible import constants as C
|
|||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.path import makedirs_safe
|
||||
from ansible.utils.unicode import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -55,6 +57,9 @@ The %s key fingerprint is %s.
|
|||
Are you sure you want to continue connecting (yes/no)?
|
||||
"""
|
||||
|
||||
# SSH Options Regex
|
||||
SETTINGS_REGEX = re.compile(r'(\w+)(?:\s*=\s*|\s+)(.+)')
|
||||
|
||||
# prevent paramiko warning noise -- see http://stackoverflow.com/questions/3920502/
|
||||
HAVE_PARAMIKO=False
|
||||
with warnings.catch_warnings():
|
||||
|
@ -121,10 +126,7 @@ SFTP_CONNECTION_CACHE = {}
|
|||
class Connection(ConnectionBase):
|
||||
''' SSH based connections with Paramiko '''
|
||||
|
||||
@property
|
||||
def transport(self):
|
||||
''' used to identify this connection object from other classes '''
|
||||
return 'paramiko'
|
||||
transport = 'paramiko'
|
||||
|
||||
def _cache_key(self):
|
||||
return "%s__%s__" % (self._play_context.remote_addr, self._play_context.remote_user)
|
||||
|
@ -137,6 +139,51 @@ class Connection(ConnectionBase):
|
|||
self.ssh = SSH_CONNECTION_CACHE[cache_key] = self._connect_uncached()
|
||||
return self
|
||||
|
||||
def _parse_proxy_command(self, port=22):
|
||||
proxy_command = None
|
||||
# Parse ansible_ssh_common_args, specifically looking for ProxyCommand
|
||||
ssh_args = [
|
||||
getattr(self._play_context, 'ssh_extra_args', ''),
|
||||
getattr(self._play_context, 'ssh_common_args', ''),
|
||||
getattr(self._play_context, 'ssh_args', ''),
|
||||
]
|
||||
if ssh_args is not None:
|
||||
args = self._split_ssh_args(' '.join(ssh_args))
|
||||
for i, arg in enumerate(args):
|
||||
if arg.lower() == 'proxycommand':
|
||||
# _split_ssh_args split ProxyCommand from the command itself
|
||||
proxy_command = args[i + 1]
|
||||
else:
|
||||
# ProxyCommand and the command itself are a single string
|
||||
match = SETTINGS_REGEX.match(arg)
|
||||
if match:
|
||||
if match.group(1).lower() == 'proxycommand':
|
||||
proxy_command = match.group(2)
|
||||
|
||||
if proxy_command:
|
||||
break
|
||||
|
||||
proxy_command = proxy_command or C.PARAMIKO_PROXY_COMMAND
|
||||
|
||||
sock_kwarg = {}
|
||||
if proxy_command:
|
||||
replacers = {
|
||||
'%h': self._play_context.remote_addr,
|
||||
'%p': port,
|
||||
'%r': self._play_context.remote_user
|
||||
}
|
||||
for find, replace in replacers.items():
|
||||
proxy_command = proxy_command.replace(find, str(replace))
|
||||
try:
|
||||
sock_kwarg = {'sock': paramiko.ProxyCommand(proxy_command)}
|
||||
display.vvv("CONFIGURE PROXY COMMAND FOR CONNECTION: %s" % proxy_command, host=self._play_context.remote_addr)
|
||||
except AttributeError:
|
||||
display.warning('Paramiko ProxyCommand support unavailable. '
|
||||
'Please upgrade to Paramiko 1.9.0 or newer. '
|
||||
'Not using configured ProxyCommand')
|
||||
|
||||
return sock_kwarg
|
||||
|
||||
def _connect_uncached(self):
|
||||
''' activates the connection object '''
|
||||
|
||||
|
@ -151,13 +198,17 @@ class Connection(ConnectionBase):
|
|||
self.keyfile = os.path.expanduser("~/.ssh/known_hosts")
|
||||
|
||||
if C.HOST_KEY_CHECKING:
|
||||
try:
|
||||
#TODO: check if we need to look at several possible locations, possible for loop
|
||||
ssh.load_system_host_keys("/etc/ssh/ssh_known_hosts")
|
||||
except IOError:
|
||||
pass # file was not found, but not required to function
|
||||
for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts", "/etc/openssh/ssh_known_hosts"):
|
||||
try:
|
||||
#TODO: check if we need to look at several possible locations, possible for loop
|
||||
ssh.load_system_host_keys(ssh_known_hosts)
|
||||
break
|
||||
except IOError:
|
||||
pass # file was not found, but not required to function
|
||||
ssh.load_system_host_keys()
|
||||
|
||||
sock_kwarg = self._parse_proxy_command(port)
|
||||
|
||||
ssh.set_missing_host_key_policy(MyAddPolicy(self._new_stdin, self))
|
||||
|
||||
allow_agent = True
|
||||
|
@ -179,6 +230,7 @@ class Connection(ConnectionBase):
|
|||
password=self._play_context.password,
|
||||
timeout=self._play_context.timeout,
|
||||
port=port,
|
||||
**sock_kwarg
|
||||
)
|
||||
except Exception as e:
|
||||
msg = str(e)
|
||||
|
@ -220,6 +272,8 @@ class Connection(ConnectionBase):
|
|||
|
||||
display.vvv("EXEC %s" % cmd, host=self._play_context.remote_addr)
|
||||
|
||||
cmd = to_bytes(cmd, errors='strict')
|
||||
|
||||
no_prompt_out = ''
|
||||
no_prompt_err = ''
|
||||
become_output = ''
|
||||
|
@ -268,7 +322,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
|
||||
|
||||
if not os.path.exists(in_path):
|
||||
if not os.path.exists(to_bytes(in_path, errors='strict')):
|
||||
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
|
||||
|
||||
try:
|
||||
|
@ -277,7 +331,7 @@ class Connection(ConnectionBase):
|
|||
raise AnsibleError("failed to open a SFTP connection (%s)" % e)
|
||||
|
||||
try:
|
||||
self.sftp.put(in_path, out_path)
|
||||
self.sftp.put(to_bytes(in_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
||||
except IOError:
|
||||
raise AnsibleError("failed to transfer file to %s" % out_path)
|
||||
|
||||
|
@ -303,7 +357,7 @@ class Connection(ConnectionBase):
|
|||
raise AnsibleError("failed to open a SFTP connection (%s)", e)
|
||||
|
||||
try:
|
||||
self.sftp.get(in_path, out_path)
|
||||
self.sftp.get(to_bytes(in_path, errors='strict'), to_bytes(out_path, errors='strict'))
|
||||
except IOError:
|
||||
raise AnsibleError("failed to transfer file from %s" % in_path)
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import os
|
|||
import pipes
|
||||
import pty
|
||||
import select
|
||||
import shlex
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
@ -32,7 +31,8 @@ from ansible import constants as C
|
|||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.path import unfrackpath, makedirs_safe
|
||||
from ansible.utils.unicode import to_bytes, to_unicode
|
||||
from ansible.utils.unicode import to_bytes, to_unicode, to_str
|
||||
from ansible.compat.six import text_type, binary_type
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -100,15 +100,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
return controlpersist, controlpath
|
||||
|
||||
@staticmethod
|
||||
def _split_args(argstring):
|
||||
"""
|
||||
Takes a string like '-o Foo=1 -o Bar="foo bar"' and returns a
|
||||
list ['-o', 'Foo=1', '-o', 'Bar=foo bar'] that can be added to
|
||||
the argument list. The list will not contain any empty elements.
|
||||
"""
|
||||
return [to_unicode(x.strip()) for x in shlex.split(to_bytes(argstring)) if x.strip()]
|
||||
|
||||
def _add_args(self, explanation, args):
|
||||
"""
|
||||
Adds the given args to self._command and displays a caller-supplied
|
||||
|
@ -157,7 +148,7 @@ class Connection(ConnectionBase):
|
|||
# Next, we add [ssh_connection]ssh_args from ansible.cfg.
|
||||
|
||||
if self._play_context.ssh_args:
|
||||
args = self._split_args(self._play_context.ssh_args)
|
||||
args = self._split_ssh_args(self._play_context.ssh_args)
|
||||
self._add_args("ansible.cfg set ssh_args", args)
|
||||
|
||||
# Now we add various arguments controlled by configuration file settings
|
||||
|
@ -196,7 +187,7 @@ class Connection(ConnectionBase):
|
|||
if user:
|
||||
self._add_args(
|
||||
"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set",
|
||||
("-o", "User={0}".format(self._play_context.remote_user))
|
||||
("-o", "User={0}".format(to_bytes(self._play_context.remote_user)))
|
||||
)
|
||||
|
||||
self._add_args(
|
||||
|
@ -210,7 +201,7 @@ class Connection(ConnectionBase):
|
|||
for opt in ['ssh_common_args', binary + '_extra_args']:
|
||||
attr = getattr(self._play_context, opt, None)
|
||||
if attr is not None:
|
||||
args = self._split_args(attr)
|
||||
args = self._split_ssh_args(attr)
|
||||
self._add_args("PlayContext set %s" % opt, args)
|
||||
|
||||
# Check if ControlPersist is enabled and add a ControlPath if one hasn't
|
||||
|
@ -230,7 +221,7 @@ class Connection(ConnectionBase):
|
|||
raise AnsibleError("Cannot write to ControlPath %s" % cpdir)
|
||||
|
||||
args = ("-o", "ControlPath={0}".format(
|
||||
C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir))
|
||||
to_bytes(C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir)))
|
||||
)
|
||||
self._add_args("found only ControlPersist; added ControlPath", args)
|
||||
|
||||
|
@ -319,8 +310,8 @@ class Connection(ConnectionBase):
|
|||
Starts the command and communicates with it until it ends.
|
||||
'''
|
||||
|
||||
display_cmd = map(pipes.quote, cmd[:-1]) + [cmd[-1]]
|
||||
display.vvv('SSH: EXEC {0}'.format(' '.join(display_cmd)), host=self.host)
|
||||
display_cmd = map(to_unicode, map(pipes.quote, cmd))
|
||||
display.vvv(u'SSH: EXEC {0}'.format(u' '.join(display_cmd)), host=self.host)
|
||||
|
||||
# Start the given command. If we don't need to pipeline data, we can try
|
||||
# to use a pseudo-tty (ssh will have been invoked with -tt). If we are
|
||||
|
@ -328,6 +319,12 @@ class Connection(ConnectionBase):
|
|||
# old pipes.
|
||||
|
||||
p = None
|
||||
|
||||
if isinstance(cmd, (text_type, binary_type)):
|
||||
cmd = to_bytes(cmd)
|
||||
else:
|
||||
cmd = list(map(to_bytes, cmd))
|
||||
|
||||
if not in_data:
|
||||
try:
|
||||
# Make sure stdin is a proper pty to avoid tcgetattr errors
|
||||
|
@ -347,7 +344,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
if self._play_context.password:
|
||||
os.close(self.sshpass_pipe[0])
|
||||
os.write(self.sshpass_pipe[1], "{0}\n".format(self._play_context.password))
|
||||
os.write(self.sshpass_pipe[1], "{0}\n".format(to_bytes(self._play_context.password)))
|
||||
os.close(self.sshpass_pipe[1])
|
||||
|
||||
## SSH state machine
|
||||
|
@ -365,7 +362,7 @@ class Connection(ConnectionBase):
|
|||
# only when using ssh. Otherwise we can send initial data straightaway.
|
||||
|
||||
state = states.index('ready_to_send')
|
||||
if 'ssh' in cmd:
|
||||
if b'ssh' in cmd:
|
||||
if self._play_context.prompt:
|
||||
# We're requesting escalation with a password, so we have to
|
||||
# wait for a password prompt.
|
||||
|
@ -463,7 +460,7 @@ class Connection(ConnectionBase):
|
|||
if states[state] == 'awaiting_prompt':
|
||||
if self._flags['become_prompt']:
|
||||
display.debug('Sending become_pass in response to prompt')
|
||||
stdin.write(self._play_context.become_pass + '\n')
|
||||
stdin.write('{0}\n'.format(to_bytes(self._play_context.become_pass )))
|
||||
self._flags['become_prompt'] = False
|
||||
state += 1
|
||||
elif self._flags['become_success']:
|
||||
|
@ -538,7 +535,7 @@ class Connection(ConnectionBase):
|
|||
stdin.close()
|
||||
|
||||
if C.HOST_KEY_CHECKING:
|
||||
if cmd[0] == "sshpass" and p.returncode == 6:
|
||||
if cmd[0] == b"sshpass" and p.returncode == 6:
|
||||
raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host\'s fingerprint to your known_hosts file to manage this host.')
|
||||
|
||||
controlpersisterror = 'Bad configuration option: ControlPersist' in stderr or 'unknown configuration option: ControlPersist' in stderr
|
||||
|
@ -555,7 +552,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
display.vvv("ESTABLISH SSH CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr)
|
||||
display.vvv(u"ESTABLISH SSH CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr)
|
||||
|
||||
# we can only use tty when we are not pipelining the modules. piping
|
||||
# data into /usr/bin/python inside a tty automatically invokes the
|
||||
|
@ -588,19 +585,19 @@ class Connection(ConnectionBase):
|
|||
|
||||
remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1
|
||||
cmd_summary = "%s..." % args[0]
|
||||
for attempt in xrange(remaining_tries):
|
||||
for attempt in range(remaining_tries):
|
||||
try:
|
||||
return_tuple = self._exec_command(*args, **kwargs)
|
||||
# 0 = success
|
||||
# 1-254 = remote command return code
|
||||
# 255 = failure from the ssh command itself
|
||||
if return_tuple[0] != 255 or attempt == (remaining_tries - 1):
|
||||
if return_tuple[0] != 255:
|
||||
break
|
||||
else:
|
||||
raise AnsibleConnectionFailure("Failed to connect to the host via ssh.")
|
||||
except (AnsibleConnectionFailure, Exception) as e:
|
||||
if attempt == remaining_tries - 1:
|
||||
raise e
|
||||
raise
|
||||
else:
|
||||
pause = 2 ** attempt - 1
|
||||
if pause > 30:
|
||||
|
@ -623,44 +620,46 @@ class Connection(ConnectionBase):
|
|||
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
display.vvv("PUT {0} TO {1}".format(in_path, out_path), host=self.host)
|
||||
if not os.path.exists(in_path):
|
||||
raise AnsibleFileNotFound("file or module does not exist: {0}".format(in_path))
|
||||
display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.host)
|
||||
if not os.path.exists(to_bytes(in_path, errors='strict')):
|
||||
raise AnsibleFileNotFound("file or module does not exist: {0}".format(to_str(in_path)))
|
||||
|
||||
# scp and sftp require square brackets for IPv6 addresses, but
|
||||
# accept them for hostnames and IPv4 addresses too.
|
||||
host = '[%s]' % self.host
|
||||
|
||||
if C.DEFAULT_SCP_IF_SSH:
|
||||
cmd = self._build_command('scp', in_path, '{0}:{1}'.format(host, pipes.quote(out_path)))
|
||||
cmd = self._build_command('scp', in_path, u'{0}:{1}'.format(host, pipes.quote(out_path)))
|
||||
in_data = None
|
||||
else:
|
||||
cmd = self._build_command('sftp', host)
|
||||
in_data = "put {0} {1}\n".format(pipes.quote(in_path), pipes.quote(out_path))
|
||||
cmd = self._build_command('sftp', to_bytes(host))
|
||||
in_data = u"put {0} {1}\n".format(pipes.quote(in_path), pipes.quote(out_path))
|
||||
|
||||
in_data = to_bytes(in_data, nonstring='passthru')
|
||||
(returncode, stdout, stderr) = self._run(cmd, in_data)
|
||||
|
||||
if returncode != 0:
|
||||
raise AnsibleError("failed to transfer file to {0}:\n{1}\n{2}".format(out_path, stdout, stderr))
|
||||
raise AnsibleError("failed to transfer file to {0}:\n{1}\n{2}".format(to_str(out_path), to_str(stdout), to_str(stderr)))
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
''' fetch a file from remote to local '''
|
||||
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
display.vvv("FETCH {0} TO {1}".format(in_path, out_path), host=self.host)
|
||||
display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.host)
|
||||
|
||||
# scp and sftp require square brackets for IPv6 addresses, but
|
||||
# accept them for hostnames and IPv4 addresses too.
|
||||
host = '[%s]' % self.host
|
||||
|
||||
if C.DEFAULT_SCP_IF_SSH:
|
||||
cmd = self._build_command('scp', '{0}:{1}'.format(host, pipes.quote(in_path)), out_path)
|
||||
cmd = self._build_command('scp', u'{0}:{1}'.format(host, pipes.quote(in_path)), out_path)
|
||||
in_data = None
|
||||
else:
|
||||
cmd = self._build_command('sftp', host)
|
||||
in_data = "get {0} {1}\n".format(pipes.quote(in_path), pipes.quote(out_path))
|
||||
in_data = u"get {0} {1}\n".format(pipes.quote(in_path), pipes.quote(out_path))
|
||||
|
||||
in_data = to_bytes(in_data, nonstring='passthru')
|
||||
(returncode, stdout, stderr) = self._run(cmd, in_data)
|
||||
|
||||
if returncode != 0:
|
||||
|
@ -674,6 +673,8 @@ class Connection(ConnectionBase):
|
|||
# temporarily disabled as we are forced to currently close connections after every task because of winrm
|
||||
# if self._connected and self._persistent:
|
||||
# cmd = self._build_command('ssh', '-O', 'stop', self.host)
|
||||
#
|
||||
# cmd = map(to_bytes, cmd)
|
||||
# p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
# stdout, stderr = p.communicate()
|
||||
|
||||
|
|
|
@ -61,8 +61,10 @@ except ImportError:
|
|||
class Connection(ConnectionBase):
|
||||
'''WinRM connections over HTTP/HTTPS.'''
|
||||
|
||||
transport = 'winrm'
|
||||
module_implementation_preferences = ('.ps1', '')
|
||||
become_methods = []
|
||||
allow_executable = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
|
@ -76,11 +78,6 @@ class Connection(ConnectionBase):
|
|||
|
||||
super(Connection, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def transport(self):
|
||||
''' used to identify this connection object from other classes '''
|
||||
return 'winrm'
|
||||
|
||||
def set_host_overrides(self, host):
|
||||
'''
|
||||
Override WinRM-specific options from host variables.
|
||||
|
@ -137,20 +134,20 @@ class Connection(ConnectionBase):
|
|||
protocol.send_message('')
|
||||
return protocol
|
||||
except Exception as e:
|
||||
err_msg = (str(e) or repr(e)).strip()
|
||||
if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I):
|
||||
err_msg = to_unicode(e).strip()
|
||||
if re.search(to_unicode(r'Operation\s+?timed\s+?out'), err_msg, re.I):
|
||||
raise AnsibleError('the connection attempt timed out')
|
||||
m = re.search(r'Code\s+?(\d{3})', err_msg)
|
||||
m = re.search(to_unicode(r'Code\s+?(\d{3})'), err_msg)
|
||||
if m:
|
||||
code = int(m.groups()[0])
|
||||
if code == 401:
|
||||
err_msg = 'the username/password specified for this server was incorrect'
|
||||
elif code == 411:
|
||||
return protocol
|
||||
errors.append('%s: %s' % (transport, err_msg))
|
||||
display.vvvvv('WINRM CONNECTION ERROR: %s\n%s' % (err_msg, traceback.format_exc()), host=self._winrm_host)
|
||||
errors.append(u'%s: %s' % (transport, err_msg))
|
||||
display.vvvvv(u'WINRM CONNECTION ERROR: %s\n%s' % (err_msg, to_unicode(traceback.format_exc())), host=self._winrm_host)
|
||||
if errors:
|
||||
raise AnsibleError(', '.join(errors))
|
||||
raise AnsibleError(', '.join(map(to_str, errors)))
|
||||
else:
|
||||
raise AnsibleError('No transport found for WinRM connection')
|
||||
|
||||
|
@ -271,7 +268,7 @@ class Connection(ConnectionBase):
|
|||
if not os.path.exists(in_path):
|
||||
raise AnsibleFileNotFound('file or module does not exist: "%s"' % in_path)
|
||||
|
||||
script_template = '''
|
||||
script_template = u'''
|
||||
begin {{
|
||||
$path = "{0}"
|
||||
|
||||
|
@ -318,7 +315,7 @@ class Connection(ConnectionBase):
|
|||
local_sha1 = secure_hash(in_path)
|
||||
|
||||
if not remote_sha1 == local_sha1:
|
||||
raise AnsibleError("Remote sha1 hash {0} does not match local hash {1}".format(remote_sha1, local_sha1))
|
||||
raise AnsibleError("Remote sha1 hash {0} does not match local hash {1}".format(to_str(remote_sha1), to_str(local_sha1)))
|
||||
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
|
|
|
@ -30,7 +30,8 @@ import traceback
|
|||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils import to_bytes
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -38,8 +39,6 @@ except ImportError:
|
|||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
BUFSIZE = 65536
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local zone based connections '''
|
||||
|
@ -56,8 +55,8 @@ class Connection(ConnectionBase):
|
|||
if os.geteuid() != 0:
|
||||
raise AnsibleError("zone connection requires running as root")
|
||||
|
||||
self.zoneadm_cmd = self._search_executable('zoneadm')
|
||||
self.zlogin_cmd = self._search_executable('zlogin')
|
||||
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)
|
||||
|
@ -86,7 +85,7 @@ class Connection(ConnectionBase):
|
|||
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', self.zone, 'list', '-p'],
|
||||
process = subprocess.Popen([self.zoneadm_cmd, '-z', to_bytes(self.zone), 'list', '-p'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
@ -113,6 +112,7 @@ class Connection(ConnectionBase):
|
|||
# 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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue