Improve the API for connection plugins and update local and ssh to use it

This commit is contained in:
Toshio Kuratomi
2015-04-15 16:32:44 -07:00
parent 1f7d23fc18
commit 01df51d2ae
5 changed files with 126 additions and 72 deletions

View File

@@ -33,15 +33,13 @@ import pty
from hashlib import sha1
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleConnectionFailure
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
from ansible.plugins.connections import ConnectionBase
class Connection(ConnectionBase):
''' ssh based connections '''
def __init__(self, connection_info, *args, **kwargs):
super(Connection, self).__init__(connection_info)
# SSH connection specific init stuff
self.HASHED_KEY_MAGIC = "|1|"
self._has_pipelining = True
@@ -52,14 +50,20 @@ class Connection(ConnectionBase):
self._cp_dir = '/tmp'
#fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN)
def get_transport(self):
super(Connection, self).__init__(connection_info)
@property
def transport(self):
''' used to identify this connection object from other classes '''
return 'ssh'
def connect(self):
def _connect(self):
''' connect to the remote host '''
self._display.vvv("ESTABLISH SSH CONNECTION FOR USER: %s" % self._connection_info.remote_user, host=self._connection_info.remote_addr)
self._display.vvv("ESTABLISH SSH CONNECTION FOR USER: {0}".format(self._connection_info.remote_user), host=self._connection_info.remote_addr)
if self._connected:
return self
self._common_args = []
extra_args = C.ANSIBLE_SSH_ARGS
@@ -67,11 +71,11 @@ class Connection(ConnectionBase):
# make sure there is no empty string added as this can produce weird errors
self._common_args += [x.strip() for x in shlex.split(extra_args) if x.strip()]
else:
self._common_args += [
self._common_args += (
"-o", "ControlMaster=auto",
"-o", "ControlPersist=60s",
"-o", "ControlPath=\"%s\"" % (C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=self._cp_dir)),
]
"-o", "ControlPath=\"{0}\"".format(C.ANSIBLE_SSH_CONTROL_PATH.format(dict(directory=self._cp_dir))),
)
cp_in_use = False
cp_path_set = False
@@ -82,30 +86,34 @@ class Connection(ConnectionBase):
cp_path_set = True
if cp_in_use and not cp_path_set:
self._common_args += ["-o", "ControlPath=\"%s\"" % (C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=self._cp_dir))]
self._common_args += ("-o", "ControlPath=\"{0}\"".format(
C.ANSIBLE_SSH_CONTROL_PATH.format(dict(directory=self._cp_dir)))
)
if not C.HOST_KEY_CHECKING:
self._common_args += ["-o", "StrictHostKeyChecking=no"]
self._common_args += ("-o", "StrictHostKeyChecking=no")
if self._connection_info.port is not None:
self._common_args += ["-o", "Port=%d" % (self._connection_info.port)]
self._common_args += ("-o", "Port={0}".format(self._connection_info.port))
# FIXME: need to get this from connection info
#if self.private_key_file is not None:
# self._common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.private_key_file)]
# self._common_args += ("-o", "IdentityFile=\"{0}\"".format(os.path.expanduser(self.private_key_file)))
#elif self.runner.private_key_file is not None:
# self._common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.runner.private_key_file)]
# self._common_args += ("-o", "IdentityFile=\"{0}\"".format(os.path.expanduser(self.runner.private_key_file)))
if self._connection_info.password:
self._common_args += ["-o", "GSSAPIAuthentication=no",
"-o", "PubkeyAuthentication=no"]
self._common_args += ("-o", "GSSAPIAuthentication=no",
"-o", "PubkeyAuthentication=no")
else:
self._common_args += ["-o", "KbdInteractiveAuthentication=no",
self._common_args += ("-o", "KbdInteractiveAuthentication=no",
"-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey",
"-o", "PasswordAuthentication=no"]
"-o", "PasswordAuthentication=no")
if self._connection_info.remote_user is not None and self._connection_info.remote_user != pwd.getpwuid(os.geteuid())[0]:
self._common_args += ["-o", "User="+self._connection_info.remote_user]
self._common_args += ("-o", "User={0}".format(self._connection_info.remote_user))
# FIXME: figure out where this goes
#self._common_args += ["-o", "ConnectTimeout=%d" % self.runner.timeout]
self._common_args += ["-o", "ConnectTimeout=15"]
#self._common_args += ("-o", "ConnectTimeout={0}".format(self.runner.timeout))
self._common_args += ("-o", "ConnectTimeout=15")
self._connected = True
return self
@@ -136,13 +144,13 @@ class Connection(ConnectionBase):
except OSError:
raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program")
(self.rfd, self.wfd) = os.pipe()
return ["sshpass", "-d%d" % self.rfd]
return ("sshpass", "-d{0}".format(self.rfd))
return []
def _send_password(self):
if self._connection_info.password:
os.close(self.rfd)
os.write(self.wfd, "%s\n" % self._connection_info.password)
os.write(self.wfd, "{0}\n".format(self._connection_info.password))
os.close(self.wfd)
def _communicate(self, p, stdin, indata, su=False, sudoable=False, prompt=None):
@@ -215,12 +223,12 @@ class Connection(ConnectionBase):
else:
user_host_file = "~/.ssh/known_hosts"
user_host_file = os.path.expanduser(user_host_file)
host_file_list = []
host_file_list.append(user_host_file)
host_file_list.append("/etc/ssh/ssh_known_hosts")
host_file_list.append("/etc/ssh/ssh_known_hosts2")
hfiles_not_found = 0
for hf in host_file_list:
if not os.path.exists(hf):
@@ -234,7 +242,7 @@ class Connection(ConnectionBase):
else:
data = host_fh.read()
host_fh.close()
for line in data.split("\n"):
if line is None or " " not in line:
continue
@@ -258,33 +266,33 @@ class Connection(ConnectionBase):
return False
if (hfiles_not_found == len(host_file_list)):
self._display.vvv("EXEC previous known host file not found for %s" % host)
self._display.vvv("EXEC previous known host file not found for {0}".format(host))
return True
def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
ssh_cmd = self._password_cmd()
ssh_cmd += ["ssh", "-C"]
ssh_cmd += ("ssh", "-C")
if not in_data:
# we can only use tty when we are not pipelining the modules. piping data into /usr/bin/python
# inside a tty automatically invokes the python interactive-mode but the modules are not
# compatible with the interactive-mode ("unexpected indent" mainly because of empty lines)
ssh_cmd += ["-tt"]
ssh_cmd.append("-tt")
if self._connection_info.verbosity > 3:
ssh_cmd += ["-vvv"]
ssh_cmd.append("-vvv")
else:
ssh_cmd += ["-q"]
ssh_cmd.append("-q")
ssh_cmd += self._common_args
# FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however
# not sure if it's all working yet so this remains commented out
#if self._ipv6:
# ssh_cmd += ['-6']
ssh_cmd += [self._connection_info.remote_addr]
ssh_cmd.append(self._connection_info.remote_addr)
ssh_cmd.append(cmd)
self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._connection_info.remote_addr)
self._display.vvv("EXEC {0}".format(' '.join(ssh_cmd)), host=self._connection_info.remote_addr)
not_in_host_file = self.not_in_host_file(self._connection_info.remote_addr)
@@ -361,7 +369,7 @@ class Connection(ConnectionBase):
# FIXME: the prompt won't be here anymore
prompt=""
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, prompt=prompt)
#if C.HOST_KEY_CHECKING and not_in_host_file:
# # lock around the initial SSH connectivity so the user prompt about whether to add
# # the host to known hosts is not intermingled with multiprocess output.
@@ -384,9 +392,9 @@ class Connection(ConnectionBase):
def put_file(self, in_path, out_path):
''' transfer a file from local to remote '''
self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr)
self._display.vvv("PUT {0} TO {1}".format(in_path, out_path), host=self._connection_info.remote_addr)
if not os.path.exists(in_path):
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
raise AnsibleFileNotFound("file or module does not exist: {0}".format(in_path))
cmd = self._password_cmd()
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH
@@ -398,12 +406,15 @@ class Connection(ConnectionBase):
# host = '[%s]' % host
if C.DEFAULT_SCP_IF_SSH:
cmd += ["scp"] + self._common_args
cmd += [in_path,host + ":" + pipes.quote(out_path)]
cmd.append('scp')
cmd += self._common_args
cmd.append(in_path,host + ":" + pipes.quote(out_path))
indata = None
else:
cmd += ["sftp"] + self._common_args + [host]
indata = "put %s %s\n" % (pipes.quote(in_path), pipes.quote(out_path))
cmd.append('sftp')
cmd += self._common_args
cmd.append(host)
indata = "put {0} {1}\n".format(pipes.quote(in_path), pipes.quote(out_path))
(p, stdin) = self._run(cmd, indata)
@@ -412,11 +423,11 @@ class Connection(ConnectionBase):
(returncode, stdout, stderr) = self._communicate(p, stdin, indata)
if returncode != 0:
raise AnsibleError("failed to transfer file to %s:\n%s\n%s" % (out_path, stdout, stderr))
raise AnsibleError("failed to transfer file to {0}:\n{1}\n{2}".format(out_path, stdout, stderr))
def fetch_file(self, in_path, out_path):
''' fetch a file from remote to local '''
self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._connection_info.remote_addr)
self._display.vvv("FETCH {0} TO {1}".format(in_path, out_path), host=self._connection_info.remote_addr)
cmd = self._password_cmd()
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH
@@ -428,21 +439,24 @@ class Connection(ConnectionBase):
# host = '[%s]' % self._connection_info.remote_addr
if C.DEFAULT_SCP_IF_SSH:
cmd += ["scp"] + self._common_args
cmd += [host + ":" + in_path, out_path]
cmd.append('scp')
cmd += self._common_args
cmd += ('{0}:{1}'.format(host, in_path), out_path)
indata = None
else:
cmd += ["sftp"] + self._common_args + [host]
indata = "get %s %s\n" % (in_path, out_path)
cmd.append('sftp')
cmd += self._common_args
cmd.append(host)
indata = "get {0} {1}\n".format(in_path, out_path)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self._send_password()
stdout, stderr = p.communicate(indata)
if p.returncode != 0:
raise AnsibleError("failed to transfer file from %s:\n%s\n%s" % (in_path, stdout, stderr))
raise AnsibleError("failed to transfer file from {0}:\n{1}\n{2}".format(in_path, stdout, stderr))
def close(self):
''' not applicable since we're executing openssh binaries '''
pass
self._connected = False