寫部署腳本時,難免涉及到一些遠程執行命令或者傳輸文件。
之前一直使用sh庫,調用sh.ssh遠程執行一些命令,sh.scp傳輸文件,但是實際使用中還是比較麻煩的,光是模擬用戶登陸這一點,還需要單獨定義方法模擬輸入。
感受一下:
from sh import sshPASS = 'xxxx'def ssh_interact(line, stdin): line = line.strip() print(line) if line.endswith('password:'): stdin.put(PASS)ssh('x.x.x.x', _out=ssh_interact)
來自官方文檔
后來發現paramiko庫更加優雅、便捷,所以準備用pramiko替換掉sh。
之前通過同事了解到,paramiko在遠程執行python腳本時,腳本中的輸出內容可能會通過stderr這個管道輸出出來,所以直接用paramiko的SSHClient類中的exec_command方法執行,通過讀stderr管道中有無輸出來判斷命令是否成功執行的方式是行不通的。所以用更底層一些的Channel類的recv_exit_status方法判斷執行退出碼更好一些。
安裝
可以通過使用pip install paramiko安裝,細節這里不再贅述。
封裝
首先定義幾個異常
# coding: utf-8import os.pathfrom paramiko import SSHClient, AutoAddPolicy, AuthenticationExceptionclass ConnectError(Exception): """ 連接錯誤時拋出的異常 """ passclass RemoteExecError(Exception): """ 遠程執行命令,失敗時拋出的異常 """ passclass 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
新聞熱點
疑難解答