python+paramiko —— run cmd through middle host

參考:

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()


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章