mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Normalize text and byte type in the ssh plugin helper method that builds up an ssh command (#17860)
Mostly cleanups to make the code more efficient, more pythonic, and obey the unicode sandwich strategy more but also Fixes #17832
This commit is contained in:
parent
b0cd624aef
commit
64c446d9c0
3 changed files with 72 additions and 58 deletions
|
@ -97,6 +97,8 @@ def _get_config(p, section, key, env_var, default):
|
|||
return value
|
||||
if p is not None:
|
||||
try:
|
||||
# TODO: Once we branch Ansible-2.2, change to the following in devel
|
||||
#return to_text(p.get(section, key, raw=True), errors='surrogate_or_strict')
|
||||
return p.get(section, key, raw=True)
|
||||
except:
|
||||
return default
|
||||
|
@ -277,7 +279,7 @@ MAX_FILE_SIZE_FOR_DIFF = get_config(p, DEFAULTS, 'max_diff_size', 'ANSIB
|
|||
|
||||
# CONNECTION RELATED
|
||||
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', '-C -o ControlMaster=auto -o ControlPersist=60s')
|
||||
ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r")
|
||||
ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', u"%(directory)s/ansible-ssh-%%h-%%p-%%r")
|
||||
ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=True)
|
||||
ANSIBLE_SSH_RETRIES = get_config(p, 'ssh_connection', 'retries', 'ANSIBLE_SSH_RETRIES', 0, integer=True)
|
||||
ANSIBLE_SSH_EXECUTABLE = get_config(p, 'ssh_connection', 'ssh_executable', 'ANSIBLE_SSH_EXECUTABLE', 'ssh')
|
||||
|
|
|
@ -140,6 +140,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
|
|||
# ['t\x00\x00\x00', '\x00\x00\x00e\x00\x00\x00']
|
||||
return [to_text(x.strip()) for x in shlex.split(to_bytes(argstring)) if x.strip()]
|
||||
except AttributeError:
|
||||
# In Python3, shlex.split doesn't work on a byte string.
|
||||
return [to_text(x.strip()) for x in shlex.split(argstring) if x.strip()]
|
||||
|
||||
@abstractproperty
|
||||
|
|
|
@ -86,7 +86,7 @@ class Connection(ConnectionBase):
|
|||
return SSHPASS_AVAILABLE
|
||||
|
||||
@staticmethod
|
||||
def _persistence_controls(command):
|
||||
def _persistence_controls(b_command):
|
||||
'''
|
||||
Takes a command array and scans it for ControlPersist and ControlPath
|
||||
settings and returns two booleans indicating whether either was found.
|
||||
|
@ -97,21 +97,29 @@ class Connection(ConnectionBase):
|
|||
controlpersist = False
|
||||
controlpath = False
|
||||
|
||||
for arg in command:
|
||||
if 'controlpersist' in arg.lower():
|
||||
for b_arg in (a.lower() for a in b_command):
|
||||
if b'controlpersist' in b_arg:
|
||||
controlpersist = True
|
||||
elif 'controlpath' in arg.lower():
|
||||
elif b'controlpath' in b_arg:
|
||||
controlpath = True
|
||||
|
||||
return controlpersist, controlpath
|
||||
|
||||
def _add_args(self, explanation, args):
|
||||
def _add_args(self, b_command, b_args, explanation):
|
||||
"""
|
||||
Adds the given args to self._command and displays a caller-supplied
|
||||
explanation of why they were added.
|
||||
Adds arguments to the ssh command and displays a caller-supplied explanation of why.
|
||||
|
||||
:arg b_command: A list containing the command to add the new arguments to.
|
||||
This list will be modified by this method.
|
||||
:arg b_args: An iterable of new arguments to add. This iterable is used
|
||||
more than once so it must be persistent (ie: a list is okay but a
|
||||
StringIO would not)
|
||||
:arg explanation: A text string containing explaining why the arguments
|
||||
were added. It will be displayed with a high enough verbosity.
|
||||
.. note:: This function does its work via side-effect. The b_command list has the new arguments appended.
|
||||
"""
|
||||
self._command += args
|
||||
display.vvvvv('SSH: ' + explanation + ': (%s)' % ')('.join(map(to_text, args)), host=self._play_context.remote_addr)
|
||||
display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self._play_context.remote_addr)
|
||||
b_command += b_args
|
||||
|
||||
def _build_command(self, binary, *other_args):
|
||||
'''
|
||||
|
@ -119,9 +127,11 @@ class Connection(ConnectionBase):
|
|||
a command line as an array that can be passed to subprocess.Popen.
|
||||
'''
|
||||
|
||||
self._command = []
|
||||
b_command = []
|
||||
|
||||
## First, the command name.
|
||||
#
|
||||
# First, the command to invoke
|
||||
#
|
||||
|
||||
# If we want to use password authentication, we have to set up a pipe to
|
||||
# write the password to sshpass.
|
||||
|
@ -131,11 +141,13 @@ class Connection(ConnectionBase):
|
|||
raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program")
|
||||
|
||||
self.sshpass_pipe = os.pipe()
|
||||
self._command += ['sshpass', '-d{0}'.format(self.sshpass_pipe[0])]
|
||||
b_command += [b'sshpass', b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')]
|
||||
|
||||
self._command += [binary]
|
||||
b_command += [to_bytes(binary, errors='surrogate_or_strict')]
|
||||
|
||||
## Next, additional arguments based on the configuration.
|
||||
#
|
||||
# Next, additional arguments based on the configuration.
|
||||
#
|
||||
|
||||
# sftp batch mode allows us to correctly catch failed transfers, but can
|
||||
# be disabled if the client side doesn't support the option. However,
|
||||
|
@ -143,98 +155,95 @@ class Connection(ConnectionBase):
|
|||
# if not using controlpersist and using sshpass
|
||||
if binary == 'sftp' and C.DEFAULT_SFTP_BATCH_MODE:
|
||||
if self._play_context.password:
|
||||
self._add_args('disable batch mode for sshpass', ['-o', 'BatchMode=no'])
|
||||
self._command += ['-b', '-']
|
||||
b_args = [b'-o', b'BatchMode=no']
|
||||
self._add_args(b_command, b_args, u'disable batch mode for sshpass')
|
||||
b_command += [b'-b', b'-']
|
||||
|
||||
if self._play_context.verbosity > 3:
|
||||
self._command += ['-vvv']
|
||||
b_command.append(b'-vvv')
|
||||
|
||||
#
|
||||
# Next, we add [ssh_connection]ssh_args from ansible.cfg.
|
||||
#
|
||||
|
||||
if self._play_context.ssh_args:
|
||||
args = self._split_ssh_args(self._play_context.ssh_args)
|
||||
self._add_args("ansible.cfg set ssh_args", args)
|
||||
b_args = [to_bytes(a, errors='surrogate_or_strict') for a in
|
||||
self._split_ssh_args(self._play_context.ssh_args)]
|
||||
self._add_args(b_command, b_args, u"ansible.cfg set ssh_args")
|
||||
|
||||
# Now we add various arguments controlled by configuration file settings
|
||||
# (e.g. host_key_checking) or inventory variables (ansible_ssh_port) or
|
||||
# a combination thereof.
|
||||
|
||||
if not C.HOST_KEY_CHECKING:
|
||||
self._add_args(
|
||||
"ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled",
|
||||
("-o", "StrictHostKeyChecking=no")
|
||||
)
|
||||
b_args = (b"-o", b"StrictHostKeyChecking=no")
|
||||
self._add_args(b_command, b_args, u"ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled")
|
||||
|
||||
if self._play_context.port is not None:
|
||||
self._add_args(
|
||||
"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set",
|
||||
("-o", "Port={0}".format(self._play_context.port))
|
||||
)
|
||||
b_args = (b"-o", b"Port=" + to_bytes(self._play_context.port, nonstring='simplerepr', errors='surrogate_or_strict'))
|
||||
self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set")
|
||||
|
||||
key = self._play_context.private_key_file
|
||||
if key:
|
||||
self._add_args(
|
||||
"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set",
|
||||
("-o", "IdentityFile=\"{0}\"".format(os.path.expanduser(key)))
|
||||
)
|
||||
b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"')
|
||||
self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set")
|
||||
|
||||
if not self._play_context.password:
|
||||
self._add_args(
|
||||
"ansible_password/ansible_ssh_pass not set", (
|
||||
"-o", "KbdInteractiveAuthentication=no",
|
||||
"-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
|
||||
"-o", "PasswordAuthentication=no"
|
||||
)
|
||||
b_command, (
|
||||
b"-o", b"KbdInteractiveAuthentication=no",
|
||||
b"-o", b"PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
|
||||
b"-o", b"PasswordAuthentication=no"
|
||||
),
|
||||
u"ansible_password/ansible_ssh_pass not set"
|
||||
)
|
||||
|
||||
user = self._play_context.remote_user
|
||||
if user:
|
||||
self._add_args(
|
||||
"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set",
|
||||
("-o", "User={0}".format(to_bytes(self._play_context.remote_user)))
|
||||
)
|
||||
self._add_args(b_command,
|
||||
(b"-o", b"User=" + to_bytes(self._play_context.remote_user, errors='surrogate_or_strict')),
|
||||
u"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set"
|
||||
)
|
||||
|
||||
self._add_args(
|
||||
"ANSIBLE_TIMEOUT/timeout set",
|
||||
("-o", "ConnectTimeout={0}".format(self._play_context.timeout))
|
||||
self._add_args(b_command,
|
||||
(b"-o", b"ConnectTimeout=" + to_bytes(self._play_context.timeout, errors='surrogate_or_strict', nonstring='simplerepr')),
|
||||
u"ANSIBLE_TIMEOUT/timeout set"
|
||||
)
|
||||
|
||||
# Add in any common or binary-specific arguments from the PlayContext
|
||||
# (i.e. inventory or task settings or overrides on the command line).
|
||||
|
||||
for opt in ['ssh_common_args', binary + '_extra_args']:
|
||||
for opt in (u'ssh_common_args', u'{0}_extra_args'.format(binary)):
|
||||
attr = getattr(self._play_context, opt, None)
|
||||
if attr is not None:
|
||||
args = self._split_ssh_args(attr)
|
||||
self._add_args("PlayContext set %s" % opt, args)
|
||||
b_args = [to_bytes(a, errors='surrogate_or_strict') for a in self._split_ssh_args(attr)]
|
||||
self._add_args(b_command, b_args, u"PlayContext set %s" % opt)
|
||||
|
||||
# Check if ControlPersist is enabled and add a ControlPath if one hasn't
|
||||
# already been set.
|
||||
|
||||
controlpersist, controlpath = self._persistence_controls(self._command)
|
||||
controlpersist, controlpath = self._persistence_controls(b_command)
|
||||
|
||||
if controlpersist:
|
||||
self._persistent = True
|
||||
|
||||
if not controlpath:
|
||||
cpdir = unfrackpath('$HOME/.ansible/cp')
|
||||
b_cpdir = to_bytes(cpdir)
|
||||
cpdir = unfrackpath(u'$HOME/.ansible/cp')
|
||||
b_cpdir = to_bytes(cpdir, errors='surrogate_or_strict')
|
||||
|
||||
# The directory must exist and be writable.
|
||||
makedirs_safe(b_cpdir, 0o700)
|
||||
if not os.access(b_cpdir, os.W_OK):
|
||||
raise AnsibleError("Cannot write to ControlPath %s" % to_native(cpdir))
|
||||
|
||||
args = ("-o", "ControlPath=" + C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir))
|
||||
self._add_args("found only ControlPersist; added ControlPath", args)
|
||||
|
||||
## Finally, we add any caller-supplied extras.
|
||||
b_args = (b"-o", b"ControlPath=" + to_bytes(C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir), errors='surrogate_or_strict'))
|
||||
self._add_args(b_command, b_args, u"found only ControlPersist; added ControlPath")
|
||||
|
||||
# Finally, we add any caller-supplied extras.
|
||||
if other_args:
|
||||
self._command += other_args
|
||||
b_command += [to_bytes(a) for a in other_args]
|
||||
|
||||
cmd = [to_bytes(a) for a in self._command]
|
||||
return cmd
|
||||
return b_command
|
||||
|
||||
def _send_initial_data(self, fh, in_data):
|
||||
'''
|
||||
|
@ -357,8 +366,10 @@ class Connection(ConnectionBase):
|
|||
raise
|
||||
os.close(self.sshpass_pipe[1])
|
||||
|
||||
## SSH state machine
|
||||
#
|
||||
# SSH state machine
|
||||
#
|
||||
|
||||
# Now we read and accumulate output from the running process until it
|
||||
# exits. Depending on the circumstances, we may also need to write an
|
||||
# escalation password and/or pipelined input to the process.
|
||||
|
|
Loading…
Reference in a new issue