2019-11-11 01:16:18 +00:00
|
|
|
from paramiko.client import SSHClient, AutoAddPolicy
|
|
|
|
from paramiko.config import SSH_PORT
|
|
|
|
from paramiko.rsakey import RSAKey
|
2019-11-18 13:04:03 +00:00
|
|
|
from paramiko.ssh_exception import AuthenticationException
|
2019-11-11 01:16:18 +00:00
|
|
|
from io import StringIO
|
|
|
|
|
|
|
|
|
|
|
|
class SSH:
|
2019-11-18 13:04:03 +00:00
|
|
|
def __init__(self, hostname, port=SSH_PORT, username='root', pkey=None, password=None, connect_timeout=10):
|
2019-11-11 01:16:18 +00:00
|
|
|
if pkey is None and password is None:
|
|
|
|
raise Exception('public key and password must have one is not None')
|
|
|
|
self.client = None
|
|
|
|
self.arguments = {
|
2019-11-18 13:04:03 +00:00
|
|
|
'hostname': hostname,
|
2019-11-11 01:16:18 +00:00
|
|
|
'port': port,
|
|
|
|
'username': username,
|
|
|
|
'password': password,
|
2019-11-18 13:04:03 +00:00
|
|
|
'pkey': RSAKey.from_private_key(StringIO(pkey)) if isinstance(pkey, str) else pkey,
|
2019-11-11 01:16:18 +00:00
|
|
|
'timeout': connect_timeout,
|
|
|
|
}
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def generate_key():
|
|
|
|
key_obj = StringIO()
|
|
|
|
key = RSAKey.generate(2048)
|
|
|
|
key.write_private_key(key_obj)
|
|
|
|
return key_obj.getvalue(), 'ssh-rsa ' + key.get_base64()
|
|
|
|
|
|
|
|
def add_public_key(self, public_key):
|
|
|
|
command = f'mkdir -p -m 700 ~/.ssh && \
|
|
|
|
echo {public_key!r} >> ~/.ssh/authorized_keys && \
|
|
|
|
chmod 600 ~/.ssh/authorized_keys'
|
2019-11-24 09:05:58 +00:00
|
|
|
code, out = self.exec_command(command)
|
2019-11-11 01:16:18 +00:00
|
|
|
if code != 0:
|
2019-11-24 09:05:58 +00:00
|
|
|
raise Exception(out)
|
2019-11-11 01:16:18 +00:00
|
|
|
|
|
|
|
def ping(self):
|
|
|
|
with self:
|
|
|
|
return True
|
|
|
|
|
2019-12-12 03:39:35 +00:00
|
|
|
def get_client(self):
|
|
|
|
if self.client is not None:
|
|
|
|
return self.client
|
|
|
|
self.client = SSHClient()
|
|
|
|
self.client.set_missing_host_key_policy(AutoAddPolicy)
|
|
|
|
self.client.connect(**self.arguments)
|
|
|
|
return self.client
|
|
|
|
|
2019-12-19 11:38:45 +00:00
|
|
|
def put_file(self, local_path, remote_path):
|
|
|
|
with self as cli:
|
|
|
|
sftp = cli.open_sftp()
|
|
|
|
sftp.put(local_path, remote_path)
|
|
|
|
sftp.close()
|
|
|
|
|
2019-11-24 09:05:58 +00:00
|
|
|
def exec_command(self, command, timeout=1800, environment=None):
|
2020-01-09 14:30:03 +00:00
|
|
|
command = 'set -e\n' + command
|
2019-11-11 01:16:18 +00:00
|
|
|
with self as cli:
|
2019-11-24 09:05:58 +00:00
|
|
|
chan = cli.get_transport().open_session()
|
|
|
|
chan.settimeout(timeout)
|
|
|
|
chan.set_combine_stderr(True)
|
|
|
|
if environment:
|
2019-12-23 14:22:48 +00:00
|
|
|
str_env = ' '.join(f"{k}='{v}'" for k, v in environment.items())
|
2019-12-18 13:19:29 +00:00
|
|
|
command = f'export {str_env} && {command}'
|
2019-11-24 09:05:58 +00:00
|
|
|
chan.exec_command(command)
|
|
|
|
out = chan.makefile("r", -1)
|
|
|
|
return chan.recv_exit_status(), out.read()
|
2019-11-11 01:16:18 +00:00
|
|
|
|
2019-11-24 09:05:58 +00:00
|
|
|
def exec_command_with_stream(self, command, timeout=1800, environment=None):
|
2020-01-09 14:30:03 +00:00
|
|
|
command = 'set -e\n' + command
|
2019-11-11 01:16:18 +00:00
|
|
|
with self as cli:
|
2019-11-24 09:05:58 +00:00
|
|
|
chan = cli.get_transport().open_session()
|
|
|
|
chan.settimeout(timeout)
|
|
|
|
chan.set_combine_stderr(True)
|
|
|
|
if environment:
|
2019-12-23 14:22:48 +00:00
|
|
|
str_env = ' '.join(f"{k}='{v}'" for k, v in environment.items())
|
2019-12-18 13:19:29 +00:00
|
|
|
command = f'export {str_env} && {command}'
|
2019-11-24 09:05:58 +00:00
|
|
|
chan.exec_command(command)
|
|
|
|
stdout = chan.makefile("r", -1)
|
|
|
|
out = stdout.readline()
|
|
|
|
while out:
|
|
|
|
yield chan.exit_status, out
|
|
|
|
out = stdout.readline()
|
2019-12-19 11:38:45 +00:00
|
|
|
yield chan.recv_exit_status(), out
|
2019-11-11 01:16:18 +00:00
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
if self.client is not None:
|
|
|
|
raise RuntimeError('Already connected')
|
2019-12-12 03:39:35 +00:00
|
|
|
return self.get_client()
|
2019-11-11 01:16:18 +00:00
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
self.client.close()
|
|
|
|
self.client = None
|