diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py index c7bbfc4f44d..8ae35a3517d 100644 --- a/lib/ansible/plugins/connection/ssh.py +++ b/lib/ansible/plugins/connection/ssh.py @@ -92,21 +92,7 @@ class Connection(ConnectionBase): # write the password to sshpass. if self._play_context.password: - global SSHPASS_AVAILABLE - - # We test once if sshpass is available, and remember the result. It - # would be nice to use distutils.spawn.find_executable for this, but - # distutils isn't always available; shutils.which() is Python3-only. - - if SSHPASS_AVAILABLE is None: - try: - p = subprocess.Popen(["sshpass"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - p.communicate() - SSHPASS_AVAILABLE = True - except OSError: - SSHPASS_AVAILABLE = False - - if not SSHPASS_AVAILABLE: + if not self._sshpass_available(): raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program") self.sshpass_pipe = os.pipe() @@ -204,36 +190,27 @@ class Connection(ConnectionBase): args = self._split_args(self.ssh_extra_args) self.add_args("inventory added ansible_ssh_extra_args", args) - # If ssh_args or ssh_extra_args set ControlPersist but not a - # ControlPath, add one ourselves. + # Check if ControlPersist is enabled (either by default, or using + # ssh_args or ssh_extra_args) and add a ControlPath if one hasn't + # already been set. - cp_in_use = False - cp_path_set = False - for arg in self._command: - if "ControlPersist" in arg: - cp_in_use = True - if "ControlPath" in arg: - cp_path_set = True + controlpersist, controlpath = self._persistence_controls(self._command) - if cp_in_use and not cp_path_set: - self._cp_dir = unfrackpath('$HOME/.ansible/cp') - - args = ("-o", "ControlPath={0}".format( - C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=self._cp_dir)) - ) - self.add_args("found only ControlPersist; added ControlPath", args) + if controlpersist: + self._persistent = True - # The directory must exist and be writable. - makedirs_safe(self._cp_dir, 0o700) - if not os.access(self._cp_dir, os.W_OK): - raise AnsibleError("Cannot write to ControlPath %s" % self._cp_dir) + if not controlpath: + cpdir = unfrackpath('$HOME/.ansible/cp') - # If the configuration dictates that we use a persistent connection, - # then we remember that for later. (We could be more thorough about - # detecting this, though.) + # The directory must exist and be writable. + makedirs_safe(cpdir, 0o700) + if not os.access(cpdir, os.W_OK): + raise AnsibleError("Cannot write to ControlPath %s" % cpdir) - if cp_in_use: - self._persistent = True + args = ("-o", "ControlPath={0}".format( + C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir)) + ) + self.add_args("found only ControlPersist; added ControlPath", args) ## Finally, we add any caller-supplied extras. @@ -646,6 +623,42 @@ class Connection(ConnectionBase): # Utility functions + def _sshpass_available(self): + global SSHPASS_AVAILABLE + + # We test once if sshpass is available, and remember the result. It + # would be nice to use distutils.spawn.find_executable for this, but + # distutils isn't always available; shutils.which() is Python3-only. + + if SSHPASS_AVAILABLE is None: + try: + p = subprocess.Popen(["sshpass"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.communicate() + SSHPASS_AVAILABLE = True + except OSError: + SSHPASS_AVAILABLE = False + + return SSHPASS_AVAILABLE + + def _persistence_controls(self, command): + ''' + Takes a command array and scans it for ControlPersist and ControlPath + settings and returns two booleans indicating whether either was found. + This could be smarter, e.g. returning false if ControlPersist is 'no', + but for now we do it simple way. + ''' + + controlpersist = False + controlpath = False + + for arg in command: + if 'controlpersist' in arg.lower(): + controlpersist = True + elif 'controlpath' in arg.lower(): + controlpath = True + + return controlpersist, controlpath + def _terminate_process(self, p): try: p.terminate()