1
0
Fork 0
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:
Toshio Kuratomi 2016-10-02 14:55:55 -07:00 committed by GitHub
parent b0cd624aef
commit 64c446d9c0
3 changed files with 72 additions and 58 deletions

View file

@ -97,6 +97,8 @@ def _get_config(p, section, key, env_var, default):
return value return value
if p is not None: if p is not None:
try: 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) return p.get(section, key, raw=True)
except: except:
return default return default
@ -277,7 +279,7 @@ MAX_FILE_SIZE_FOR_DIFF = get_config(p, DEFAULTS, 'max_diff_size', 'ANSIB
# CONNECTION RELATED # CONNECTION RELATED
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', '-C -o ControlMaster=auto -o ControlPersist=60s') 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_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_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') ANSIBLE_SSH_EXECUTABLE = get_config(p, 'ssh_connection', 'ssh_executable', 'ANSIBLE_SSH_EXECUTABLE', 'ssh')

View file

@ -140,6 +140,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
# ['t\x00\x00\x00', '\x00\x00\x00e\x00\x00\x00'] # ['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()] return [to_text(x.strip()) for x in shlex.split(to_bytes(argstring)) if x.strip()]
except AttributeError: 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()] return [to_text(x.strip()) for x in shlex.split(argstring) if x.strip()]
@abstractproperty @abstractproperty

View file

@ -86,7 +86,7 @@ class Connection(ConnectionBase):
return SSHPASS_AVAILABLE return SSHPASS_AVAILABLE
@staticmethod @staticmethod
def _persistence_controls(command): def _persistence_controls(b_command):
''' '''
Takes a command array and scans it for ControlPersist and ControlPath Takes a command array and scans it for ControlPersist and ControlPath
settings and returns two booleans indicating whether either was found. settings and returns two booleans indicating whether either was found.
@ -97,21 +97,29 @@ class Connection(ConnectionBase):
controlpersist = False controlpersist = False
controlpath = False controlpath = False
for arg in command: for b_arg in (a.lower() for a in b_command):
if 'controlpersist' in arg.lower(): if b'controlpersist' in b_arg:
controlpersist = True controlpersist = True
elif 'controlpath' in arg.lower(): elif b'controlpath' in b_arg:
controlpath = True controlpath = True
return controlpersist, controlpath 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 Adds arguments to the ssh command and displays a caller-supplied explanation of why.
explanation of why they were added.
: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(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self._play_context.remote_addr)
display.vvvvv('SSH: ' + explanation + ': (%s)' % ')('.join(map(to_text, args)), host=self._play_context.remote_addr) b_command += b_args
def _build_command(self, binary, *other_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. 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 # If we want to use password authentication, we have to set up a pipe to
# write the password to sshpass. # 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") raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program")
self.sshpass_pipe = os.pipe() 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 # sftp batch mode allows us to correctly catch failed transfers, but can
# be disabled if the client side doesn't support the option. However, # 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 not using controlpersist and using sshpass
if binary == 'sftp' and C.DEFAULT_SFTP_BATCH_MODE: if binary == 'sftp' and C.DEFAULT_SFTP_BATCH_MODE:
if self._play_context.password: if self._play_context.password:
self._add_args('disable batch mode for sshpass', ['-o', 'BatchMode=no']) b_args = [b'-o', b'BatchMode=no']
self._command += ['-b', '-'] self._add_args(b_command, b_args, u'disable batch mode for sshpass')
b_command += [b'-b', b'-']
if self._play_context.verbosity > 3: if self._play_context.verbosity > 3:
self._command += ['-vvv'] b_command.append(b'-vvv')
#
# Next, we add [ssh_connection]ssh_args from ansible.cfg. # Next, we add [ssh_connection]ssh_args from ansible.cfg.
#
if self._play_context.ssh_args: if self._play_context.ssh_args:
args = self._split_ssh_args(self._play_context.ssh_args) b_args = [to_bytes(a, errors='surrogate_or_strict') for a in
self._add_args("ansible.cfg set ssh_args", args) 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 # Now we add various arguments controlled by configuration file settings
# (e.g. host_key_checking) or inventory variables (ansible_ssh_port) or # (e.g. host_key_checking) or inventory variables (ansible_ssh_port) or
# a combination thereof. # a combination thereof.
if not C.HOST_KEY_CHECKING: if not C.HOST_KEY_CHECKING:
self._add_args( b_args = (b"-o", b"StrictHostKeyChecking=no")
"ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled", self._add_args(b_command, b_args, u"ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled")
("-o", "StrictHostKeyChecking=no")
)
if self._play_context.port is not None: if self._play_context.port is not None:
self._add_args( b_args = (b"-o", b"Port=" + to_bytes(self._play_context.port, nonstring='simplerepr', errors='surrogate_or_strict'))
"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set", self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set")
("-o", "Port={0}".format(self._play_context.port))
)
key = self._play_context.private_key_file key = self._play_context.private_key_file
if key: if key:
self._add_args( b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"')
"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set", self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set")
("-o", "IdentityFile=\"{0}\"".format(os.path.expanduser(key)))
)
if not self._play_context.password: if not self._play_context.password:
self._add_args( self._add_args(
"ansible_password/ansible_ssh_pass not set", ( b_command, (
"-o", "KbdInteractiveAuthentication=no", b"-o", b"KbdInteractiveAuthentication=no",
"-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey", b"-o", b"PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
"-o", "PasswordAuthentication=no" b"-o", b"PasswordAuthentication=no"
) ),
u"ansible_password/ansible_ssh_pass not set"
) )
user = self._play_context.remote_user user = self._play_context.remote_user
if user: if user:
self._add_args( self._add_args(b_command,
"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set", (b"-o", b"User=" + to_bytes(self._play_context.remote_user, errors='surrogate_or_strict')),
("-o", "User={0}".format(to_bytes(self._play_context.remote_user))) u"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set"
) )
self._add_args( self._add_args(b_command,
"ANSIBLE_TIMEOUT/timeout set", (b"-o", b"ConnectTimeout=" + to_bytes(self._play_context.timeout, errors='surrogate_or_strict', nonstring='simplerepr')),
("-o", "ConnectTimeout={0}".format(self._play_context.timeout)) u"ANSIBLE_TIMEOUT/timeout set"
) )
# Add in any common or binary-specific arguments from the PlayContext # Add in any common or binary-specific arguments from the PlayContext
# (i.e. inventory or task settings or overrides on the command line). # (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) attr = getattr(self._play_context, opt, None)
if attr is not None: if attr is not None:
args = self._split_ssh_args(attr) b_args = [to_bytes(a, errors='surrogate_or_strict') for a in self._split_ssh_args(attr)]
self._add_args("PlayContext set %s" % opt, args) 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 # Check if ControlPersist is enabled and add a ControlPath if one hasn't
# already been set. # already been set.
controlpersist, controlpath = self._persistence_controls(self._command) controlpersist, controlpath = self._persistence_controls(b_command)
if controlpersist: if controlpersist:
self._persistent = True self._persistent = True
if not controlpath: if not controlpath:
cpdir = unfrackpath('$HOME/.ansible/cp') cpdir = unfrackpath(u'$HOME/.ansible/cp')
b_cpdir = to_bytes(cpdir) b_cpdir = to_bytes(cpdir, errors='surrogate_or_strict')
# The directory must exist and be writable. # The directory must exist and be writable.
makedirs_safe(b_cpdir, 0o700) makedirs_safe(b_cpdir, 0o700)
if not os.access(b_cpdir, os.W_OK): if not os.access(b_cpdir, os.W_OK):
raise AnsibleError("Cannot write to ControlPath %s" % to_native(cpdir)) raise AnsibleError("Cannot write to ControlPath %s" % to_native(cpdir))
args = ("-o", "ControlPath=" + C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir)) b_args = (b"-o", b"ControlPath=" + to_bytes(C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir), errors='surrogate_or_strict'))
self._add_args("found only ControlPersist; added ControlPath", args) self._add_args(b_command, b_args, u"found only ControlPersist; added ControlPath")
## Finally, we add any caller-supplied extras.
# Finally, we add any caller-supplied extras.
if other_args: 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 b_command
return cmd
def _send_initial_data(self, fh, in_data): def _send_initial_data(self, fh, in_data):
''' '''
@ -357,8 +366,10 @@ class Connection(ConnectionBase):
raise raise
os.close(self.sshpass_pipe[1]) os.close(self.sshpass_pipe[1])
## SSH state machine
# #
# SSH state machine
#
# Now we read and accumulate output from the running process until it # Now we read and accumulate output from the running process until it
# exits. Depending on the circumstances, we may also need to write an # exits. Depending on the circumstances, we may also need to write an
# escalation password and/or pipelined input to the process. # escalation password and/or pipelined input to the process.