參考:
1. https://stackoverflow.com/questions/42208655/paramiko-nest-ssh-session-to-another-machine-while-preserving-paramiko-function
2. https://github.com/paramiko/paramiko/issues/109
目的:代碼在本地,需要一個mid_host 做中轉,才能連接到end_host,並最終讓code運行在end_host上。
參考1寫的非常好,但是如果所執行的命令不能在一瞬間完成,會導致命令只運行了一點,比如我做得是解壓文件,文件解壓了一半就終止了,因此參考2,對代碼進行改進
我的code:
其中有兩行在測試的時候不加是沒有問題的,可是我的程序必須要加,不然會導致hang
可以找一個比較大的壓縮包來測試
對於爲什麼要加
ParaProxy 這個類,如果用默認的ProxyCommand ,mid_host在連接的時候需要免密,在stackoverflow上也有例子
import paramiko
import socket
from paramiko.ssh_exception import SSHException
class ParaProxy(paramiko.util.ClosingContextManager):
"""
Instead of ProxyCommand.
Solving ProxyCommand cannot connect to machine with password.
"""
def __init__(self, stdin, stdout, stderr):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.timeout = None
def send(self, content):
try:
self.stdin.write(content)
except IOError as exc:
print('IOError exception.')
return
return len(content)
def recv(self, size):
buffer = b''
start = time.time()
while len(buffer) < size:
if self.timeout is not None:
elapsed = (time.time() - start)
if elapsed >= self.timeout:
raise socket.timeout()
buffer += self.stdout.read(size - len(buffer))
return buffer
def close(self):
self.stdin.close()
self.stdout.close()
self.stderr.close()
def settimeout(self, timeout):
self.timeout = timeout
def run_cmd_between_remotes(mid_host_ip, mid_host_user, mid_host_password,
end_host_ip, end_host_user, end_host_password,
cmd, cmd_mid=None, timeout=10):
"""
ssh to another machine via a middle machine.
:param mid_host_ip:
:param mid_host_user:
:param mid_host_password:
:param end_host_ip:
:param end_host_user:
:param end_host_password:
:param cmd: The cmd need to run on end host
:param cmd_mid: The cmd need to run on mid host
:param timeout: Time to delay
:return:
"""
# Connect to middle machine
mid_cli = paramiko.SSHClient()
mid_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
mid_cli.connect(hostname=mid_host_ip, username=mid_host_user,
password=mid_host_password)
logging.info('Connect to %s successfully !' % mid_host_ip)
if cmd_mid:
stdin, stdout, stderr = mid_cli.exec_command(cmd_mid)
logging.debug('stdout %s' % (''.join(stdout.readlines())))
error_msg = ''.join(stderr.readlines())
if error_msg != '':
raise SSHException(error_msg)
else:
logging.info('run cmd %s on %s successfully!' % (cmd, mid_host_ip))
stderr.close()
stdout.close()
io_tupple = mid_cli.exec_command('nc %s %s' % (end_host_ip, 22))
proxy = ParaProxy(*io_tupple)
proxy.settimeout(1000)
# Connecting to AnotherMachine and executing... anything...
end_cli = paramiko.SSHClient()
end_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
end_cli.connect(hostname=end_host_ip, username=end_host_user,
password=end_host_password, sock=proxy)
logging.info('Connect to %s successfully !' % end_host_ip)
logging.info('run cmd %s.......' % cmd)
stdin, stdout, stderr = end_cli.exec_command(cmd, timeout=timeout)
# waiting for exit status (that means cmd finished)
exit_status = stdout.channel.recv_exit_status()
# flush commands and cut off more writes
# if del follow two line, it will cause my code hang, but when i just test ,it runs ok
stdin.flush()
stdin.channel.shutdown_write()
# close connection
end_cli.close()
mid_cli.close()
logging.debug('stdout %s' % (''.join(stdout.readlines())))
error_msg = ''.join(stderr.readlines())
if error_msg != '':
raise SSHException('ERROR STATUS %s ; ERROR MSG %s' % (exit_status,
error_msg))
else:
logging.info('run cmd %s on %s successfully!' % (cmd, end_host_ip))
stderr.close()
stdout.close()