Commit 30fb722a authored by Simon Redman's avatar Simon Redman

Move generic command-running code to ssh_helper

parent bc6caae7
......@@ -20,6 +20,10 @@ import sys
from pexpect import pxssh
DEFAULT_SSH_OPTIONS = {"StrictHostKeyChecking": "no",
"UserKnownHostsFile": "/dev/null"}
def login_password_failover(session, hostname, username, password=""):
"""
Attempt to log in a pxssh session using only ssh agent-provided public keys, then failover to requesting a password
......@@ -58,3 +62,70 @@ def decode_output(session, encoding=sys.stdout.encoding):
:return: string-form of the read bytes
"""
return str(session.before.decode(encoding))
def run_command_on_host(command, hostname, username, password="", ssh_options=DEFAULT_SSH_OPTIONS):
"""
Run a specified command on a single host
:param command: command to run
:param hostname: hostname (or IP address) to SSH to
:param username: ssh username
:param password: ssh password - Will prompt if not provided and in-memory SSH keys don't work
:param ssh_options: ssh options as would be in an ssh config file
:return: password used to log in to this host, if used, otherwise empty string
"""
session = pxssh.pxssh(options=ssh_options)
password = login_password_failover(session, hostname, username, password)
session.sendline(command)
session.prompt()
session.logout()
return password
def run_commands_on_network(commands, hostnames, usernames, passwords=None, ssh_options=DEFAULT_SSH_OPTIONS):
"""
Add the specified command on each host in the network
:param commands: list of commands to run, one per host
:param hostnames: list of hosts on which commands should be run
:param usernames: list of usernames to use to log in to each host
:param passwords: (optional) list of passwords to log in to each host
:param ssh_options: options to pass to ssh, as per pxssh documentation
:return: list of passwords which were used to log in to the servers, where empty string means no password was used
"""
num_hosts = len(hostnames)
assert len(usernames) == num_hosts, "Please provide one username for every host"
assert len(commands) == num_hosts, "Please provide one command for every host"
if passwords is None:
# Generate empty passwords for every host if no passwords were provided
passwords = ["" for i in range(len(usernames))]
assert len(passwords) == num_hosts, "If provided, there must be one password per username"
for host_idx in range(0, num_hosts):
command = commands[host_idx]
hostname = hostnames[host_idx]
username = usernames[host_idx]
password = passwords[host_idx]
if passwords == "":
# To try to avoid prompting for passwords, try the previously-used password first
password = passwords[host_idx - 1]
try:
returned_password = run_command_on_host(command, hostname, username, password, ssh_options)
except pxssh.ExceptionPxssh as e:
print("Unable to log in to {user}@{host} using in-memory SSH keys nor provided password".format(user=username, host=hostname), file=sys.stderr)
print(e, file=sys.stderr)
continue
# Save a successful password to avoid prompting the user in the future
if returned_password == "":
# Probably key-based login was successful, in which case updating the list of passwords is meaningless
pass
else:
passwords[host_idx] = returned_password
return passwords
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment