1. 程式人生 > >paramiko模塊

paramiko模塊

whoami red ram result home lob sport authent 連接

paramiko在遠程執行python腳本時,腳本中的輸出內容可能會通過stderr這個管道輸出出來,所以直接用paramiko的SSHClient類中的exec_command方法執行,通過讀stderr管道中有無輸出來判斷命令是否成功執行的方式是行不通的。所以用更底層一些的Channel類的recv_exit_status方法判斷執行退出碼更好一些。

我們先來一個示例,簡單認識一下paramiko模塊的使用

# 用戶名和密碼方式
# import paramiko
# ssh = paramiko.SSHClient()
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# ssh.connect(hostname=‘192.168.16.72‘, port=22, username=‘root‘, password=‘redhat‘)
# stdin, stdout, stderr = ssh.exec_command(‘ifconfig‘)
# result = stdout.read()
# ssh.close()
# print(result)

# 公鑰私鑰方式
# import paramiko
# private_key = paramiko.RSAKey.from_private_key_file(‘/home/auto/.ssh/id_rsa‘)
# ssh = paramiko.SSHClient()
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# ssh.connect(hostname=‘c1.salt.com‘, port=22, username=‘wupeiqi‘, pkey=private_key)
# stdin, stdout, stderr = ssh.exec_command(‘df‘)
# result = stdout.read()
# ssh.close()

在舉一個示例,較深入認識一下paramiko模塊的使用

首先定義幾個異常:

# coding: utf-8
import os.path

from paramiko import SSHClient, AutoAddPolicy, AuthenticationException


class ConnectError(Exception):
    """
    連接錯誤時拋出的異常
    """
    pass

class RemoteExecError(Exception):
    """
    遠程執行命令,失敗時拋出的異常
    """
    pass

class SCPError(Exception):
    """
    遠程下發文件時拋出的異常
    """
    pass
class Remote(object):
    def __init__(self, host, username, password=None, port=22, key_filename=None):
        self.host = host
        self.username = username
        self.password = password
        self.port = port
        self.key_filename = key_filename
        self._ssh = None

    def _connect(self):
        self._ssh = SSHClient()
        self._ssh.set_missing_host_key_policy(AutoAddPolicy())
        try:
            if self.key_filename:
                self._ssh.connect(self.host, username=self.username, port=self.port, key_filename=self.key_filename)
            else:
                self._ssh.connect(self.host, username=self.username, password=self.password, port=self.port)
        except AuthenticationException: 
            self._ssh = None
            raise ConnectionError(‘連接失敗,請確認用戶名、密碼、端口或密鑰文件是否有效‘)
        except Exception as e:
            self._ssh = None
            raise ConnectionError(‘連接時出現意料外的錯誤:%s‘ % e)

    def get_ssh(self):
        if not self._ssh:
            self._connect()
        return self._ssh

實例化SSHClient類,通過它的connect()方法獲取SSH連接。

需要註意的是,遠程訪問的主機若是第一次連接,屬於未知設備需要認證,通過set_missing_host_key_policy()方法設置一種策略,這裏使用的是AutoAddPolicy()

這裏的_connect支持兩種方式登錄,一種是提供主機的用戶名密碼,另一種是通過密鑰文件。在連接時檢查如果指定了密鑰文件則使用這種方式登錄,否則通過用戶名密碼登錄。

_connect()雖然是實際的建立連接的方法,但實際對外接口是get_ssh(),如果已經有建立好的SSH連接直接返回,避免重復建立連接。

class Remote(object):
    ...

    
    def ssh(self, cmd, root_password=None, get_pty=False, super=False):
        cmd = self._prepare_cmd(cmd, root_password, super)
        stdout = self._exec(cmd, get_pty)
        return stdout

    def _prepare_cmd(self, cmd, root_password=None, super=False):
        if self.username != ‘root‘ and super:
            if root_password:
                cmd = "echo ‘{}‘|su - root -c ‘{}‘".format(root_password, cmd)
            else:
                cmd = "echo ‘{}‘|sudo -p ‘‘ -S su - root -c ‘{}‘".format(self.password, cmd)
        return cmd

    def _exec(self, cmd, gty_pty=False):
        channel = self.get_ssh().get_transport().open_session()
        if get_pty:
            channel.get_pty()
        channel.exec_command(cmd)
        stdout = channel.makefile(‘r‘, -1).readlines()
        stderr = channel.makefile_stderr(‘r‘, -1).readlines()
        ret_code = channel.recv_exit_status()
        if ret_code:
            msg = ‘‘.join(stderr) if stderr else ‘‘.join(stdout)
            raise RemoteExecError(msg)
        return stdout

在遠程執行某些命令時,可能需要管理員權限,這種時候需要做一些判斷,首先判斷登錄提供的用戶名如果不是root,則需要對命令做一些修改。這裏的修改有兩種情況,一是,該普通用戶本身就有sudo權限,只需要把執行的命令加到sudo之後執行就可以,還有一種是普通用戶沒有sudo權限,需要通過su先切換到root身份之後再執行,這種情況下需要提供root密碼。

還有一點要註意的是get_pty這個參數,實際在遠程執行sudo命令時,一般主機都會需要通過tty才能執行,通過把get_pty值設置為True,可以模擬tty,但是隨之而來也會有一個問題,如果是遠程執行一個需要長期運行的進程,例如啟動nginx服務,當遠程命令執行後SSH退出之後,此次運行的所有程序也會隨之結束,所以在需要通過遠程命令運行某些服務或程序時,是不能指定get_pty參數的;但同時,如果是普通用戶遠程登錄,是沒有權限執行service命令的。建議的一種方式是修改/etc/sudoers配置文件,註釋掉Defaults requiretty這行。

class Remote(object):
    ...

    def scp(self, local_file, remote_path):
        if not os.path.exists(local_file):
            raise SCPError("Local %s isn‘t exists" % local_file)
        if not os.path.isfile(local_file):
            raise SCPError("%s is not a File" % local_file)
        sftp = self.get_ssh().open_sftp()
        try:
            sftp.put(local_file, remote_path)
        except Exception as e:
            raise SCPError(e)

  使用

# coding: utf-8
from remote_client import RemoteClient

rc = RemoteClient(‘10.1.100.1‘, ‘test‘, ‘test_pass‘)
rc.ssh(‘whoami‘)   # [u‘test\n‘]
rc.scp(‘/tmp/test.out‘, ‘/tmp/test.out‘)

  

paramiko模塊