Python3 使用paramiko模塊構建Server類,實現讀取配置文件、ssh發送無交互指令、sftp上傳下載、channel發送指令可以支持交互指令

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Rache
# date:2020/3/27 20:45
# desc:老闆你再這樣我要刪庫了
import os
import time

import paramiko
from time import sleep

import pandas as pd


class Server(object):
    """
    構建一個公共服務類
    這裏說明一下:
    遠端服務器爲 linux
    本地服務器爲 windows 運行本腳本的服務器
    """
    def __init__(self, ip='', port=22, username='', password='', timeout=30):
        """
        通過IP, 端口,用戶名,密碼,超時時間,初始化一個遠程主機
        :param str ip:
        :param int port: default value is 22
        :param str username:
        :param str password:
        :param int timeout: default value is 30.
        """
        # 連接信息
        self._ip = ip
        self._port = port
        self._username = username
        self._password = password
        self._timeout = timeout
        # transport, channel, ssh, sftp, prompt
        self._transport = None
        self._channel = None
        self._ssh = None
        self._sftp = None
        self._prompt = None
        # 連接失敗的重試次數(總計3次嘗試)
        self._try_times = 2

    # 調用connect方法連接遠程主機
    def connect(self):
        """
        :return: result
        """
        _result = ''
        while True:
            # 嘗試連接
            try:
                # 創建一個通道
                self._transport = paramiko.Transport((self._ip, self._port))
                self._transport.connect(username=self._username, password=self._password)
                # 如果沒有拋出異常說明連接成功,直接返回
                _result += '%s con 創建成功' % self._ip
                break
            # 這裏對可能的異常如網絡不通、鏈接超時、socket.error, socket.timeout直接輸出
            except Exception as _e:
                if self._try_times != 0:
                    _result += '第%i次連接 %s 異常,原因:%s\n' % (3 - self._try_times, self._ip, _e)
                    _result += '進行重試\n'
                    self._try_times -= 1
                else:
                    _result += '第%i次連接 %s 異常,原因:%s\n' % (3 - self._try_times, self._ip, _e)
                    _result += '連接遠程主機 %s 失敗,結束重試' % self._ip
                    break
        return _result

    # 開啓ssh
    def open_ssh(self):
        # 連接ssh
        try:
            # 實例化SSHClient
            self._ssh = paramiko.SSHClient()
            # 這行一定要呀  大概是允許連接不在know_hosts文件中的主機 # 設置新主機和密碼管理策略, 接受一個參數, 值可以是 AutoAddPolicy、RejectPolicy、WarningPolicy, 默認爲 RejectPolicy
            self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self._ssh._transport = self._transport
            return '%s ssh 連接成功' % self._ip
        except Exception as _e:
            return '%s ssh 連接異常:%s' % (self._ip, _e)

    # ssh發送無需交互的單條命令
    def ssh_send_cmd(self, cmd):
        """
        僅支持無需交互的指令
        :param str cmd:
        :return: str stdout、str stderr
        """
        try:
            _stdin, _stdout, _stderr = self._ssh.exec_command(cmd)
            # 返回decode的指令stdout和stderr信息
            return _stdout.read().decode(), _stderr.read().decode()
        except Exception as _e:
            return 'ssh指令執行異常:%s' % _e

    # 檢查遠程服務器上文件是否存在
    def exists_remote(self, remotefile):
        """
        檢查遠程服務器上的文件是否存在
        :param remotefile: 遠端的絕對路徑+文件名
        :return: ls 目錄檢查的正確結果
        """
        try:
            cmd='ls '+ remotefile
            _stdin, _stdout, _stderr = self._ssh.exec_command(cmd)
            # 返回decode的指令stdout和stderr信息
            return _stdout.read().decode()
        except Exception as _e:
            return '"%s" 指令執行異常:%s' % (cmd,_e)

    # 檢查本地服務器上文件是否存在
    def exists_local(self,localfile):
        """
        檢查本地服務器上的文件是否存在
        :param localfile: 本地的絕對路徑+文件名
        :return: 檢查結果
        """
        if not os.path.exists(localfile ):
            return '檢查本地文件路徑不通過:%s 目錄或文件不存在' % localfile
        else:
            return True

    # mv 遠程服務器上文件至指定目錄
    def mv_remote(self,originalFile, targetFile):
        """
        備份原文件
        :param OriginalFile: 遠端的絕對路徑+文件名
        :param targetFile: 遠端的備份路徑+文件名+時間戳
        :return: mv 命令執行的正確結果
        """
        try:
            cmd ='mv '+ originalFile +' ' +targetFile+'.bak-'+self.get_current_time()
            if self.exists_remote(originalFile)=="":
                return '"%s" 指令未執行:原文件不存在!' % cmd
            else:
                _stdin, _stdout, _stderr = self._ssh.exec_command(cmd)
                # 返回decode的指令stdout和stderr信息
                r=_stdout.read().decode()
                if r=="":
                    return '"%s" 指令執行成功' % cmd
                else:
                    return '"%s" 指令執行失敗:%s' % (cmd, r)
        except Exception as _e:
            return '"%s" 指令執行異常:%s' % (cmd, _e)

    # 開啓sftp
    def open_sftp(self):
        # 連接sftp
        try:
            self._sftp = paramiko.SFTPClient.from_transport(self._transport)
            return '%s sftp 連接成功' % self._ip
        except Exception as _e:
            return '%s sftp 連接異常:%s' % (self._ip, _e)

    # sftp get單個文件
    def sftp_get(self, remotefile='', localfile=''):
        """
        :param str remotefile: 遠端的絕對路徑+文件名
        :param str localfile: 本地的絕對路徑+文件名
        :return: 下載結果
        """
        try:
            self._sftp.get(remotefile, localfile)
            return '%s 下載成功' % remotefile
        except Exception as e:
            return '%s 下載異常:%s' % (remotefile, e)

    # sftp put單個文件
    def sftp_put(self, localfile='', remotefile=''):
        """
        :param str localfile: 本地的絕對路徑+文件名
        :param str remotefile: 遠端的絕對路徑+文件名
        :return: 上傳結果
        """
        try:
            self._sftp.put(localfile, remotefile)
            return '✔ 上傳成功:%s to %s ' % (localfile,remotefile)
        except Exception as e:
            return '✘ 上傳異常(%s ):%s' % (localfile, e)

    # 開啓channel
    def open_channel(self):
        """
        :return: result
        """
        _result = ''
        try:
            self._channel = self._transport.open_session()
            self._channel.settimeout(self._timeout)
            self._channel.get_pty()
            self._channel.invoke_shell()
            # 如果沒有拋出異常說明連接成功
            _result += '%s channel 建立成功' % self._ip
            sleep(1)  # 等待1秒,接收SSH banner信息
            _Banner = self._channel.recv(65535)  # 接收ssh banner信息
        except Exception as _e:
            _result += '%s channel 建立異常:%s' % (self._ip, _e)
        return _result

    # 獲取channel提示符
    def get_prompt(self, expect_symbol=''):
        """
        :param str expect_symbol: The prompt's symbol,like '>','# ','$ ',etc.
        :return: result
        """
        _result = ''
        try:
            # 發送"Enter"獲取提示符
            n = 0
            while n < 3:
                self._channel.send("\r")
                # 暫停0.5~1秒接收輸入回車後的返回結果
                sleep(0.5)
                _Prompt_vendor = self._channel.recv(64)
                # 獲取提示符的兩種方式:
                # 1. 按\r\n進行字符串分割,後邊的就是完整的提示符
                self._prompt = _Prompt_vendor.decode('utf-8').split('\r\n')[-1]
                # 2. 提示符取輸出的後x位,即_Prompt_vendor[-x:]
                # self._prompt = _Prompt_vendor[-2:].decode('utf-8')
                # 如果獲取的提示符由期待的提示符末尾標識符結尾,判斷爲獲取成功
                if self._prompt.endswith(expect_symbol):
                    _result += '提示符獲取成功(%s)' % self._prompt
                    break
                n += 1
            else:
                _result += '提示符獲取異常(%s)' % self._prompt
        except Exception as _e:
            _result += '提示符獲取異常:%s' % _e
        return _result

    # 通過channel發送指令,返回執行結果。如果指令是交互指令,則需要給出交互的斷點提示符
    def channel_send_cmd(self, cmd='', break_prompt=''):
        """
        通過channel發送指令。
        如果是交互式指令,必須要給出break_prompt!用來判斷斷點,結束while循環,返回結果
        無需交互的指令,break_prompt空着就行
        :param str cmd: 執行的指令,支持交互指令
        :param str break_prompt: 判斷指令結束/斷點的提示符。默認爲channel的提示符
        :return: result
        """
        _stream = ''
        if not break_prompt:
            break_prompt = self._prompt
        try:
            cmd += '\r'
            # 通過提示符來判斷命令是否執行完成
            # 發送要執行的命令
            self._channel.send(cmd)
            # 回顯很長的命令可能執行較久,通過循環分批次取回回顯
            while True:
                sleep(0.5)
                _stream += self._channel.recv(1024).decode('utf-8')
                if _stream.endswith(break_prompt):
                    break
            return _stream
        except Exception as _e:
            return 'channel執行指令異常:%s' % _e

    # 釋放資源
    def close(self):
        # 斷開連接
        if self._ssh:
            self._ssh.close()
        if self._channel:
            self._channel.close()
        if self._transport:
            self._transport.close()
        return '%s 連接已關閉' % self._ip

    def __del__(self):
        return

    # 獲取時間戳
    def get_current_time(self):
        """
        獲取當前時間時間戳
        :return: 格式化後的時間戳
        """
        t=time.time()
        t_format=time.strftime('%Y-%m-%d-%H%M%S ',time.localtime(time.time()))
        return t_format


if __name__ == '__main__':
    conf_file = 'E:\\config.txt'
    local_dir='E:\\version-latest'
    remote_dir='/home/xxx/appBase'
    remote_bak_dir = '/home/xxx/dbbak'

    # 讀取本地配置文件
    pd.read_csv(conf_file, sep=' ')
    hostList = pd.read_table(conf_file, encoding='gb2312', delim_whitespace=True, index_col=0)
    for index, row in hostList.iterrows():
        print(index)
        # print("ip:" + row["ip"], "uname:" + row["uname"], "pwd:" + row["pwd"], "filename:" + row["filename"])  # 輸出各列
        ip=row["ip"]
        username=str(row["uname"])
        password=str(row["pwd"])
        filename=str(row["filename"])
        localfile=str(local_dir+os.sep+filename)
        remotefile=str(remote_dir+'/'+filename)
        remotebakfile=str(remote_bak_dir+'/'+filename)

        s=Server(ip,22,username,password) # 實例化
        check=s.exists_local(localfile) # 檢查本地路徑文件是否存在
        if check==True: # 如果存在,繼續
            try:
                print(s.connect())      # 創建一個連接
                print(s.open_ssh())     # 開啓ssh
                print(s.open_sftp())  # 開啓sftp
                # print(s.mv_remote(remotefile,remotebakfile)) # 原文件至dbbak
                # print(s.sftp_put(localfile,remotefile)) # 上傳文件

                # ssh發送無交互的指令
                print(s.ssh_send_cmd('ls /home/yunmas'))
                print('- - - - - - - - - - - -')
                # 使用channel發送交互指令
                print(s.open_channel())
                print(s.get_prompt(expect_symbol='# '))
                print(s.channel_send_cmd('df'))

            except Exception as _e:
                print( '異常:%s' % _e)
            finally:
                if s:
                    print(s.close())
                    del s
        else: # 如果不存在,返回錯誤信息
            print(check)




config.txt 模板:

excel編輯如下表格模板後直接全部拷貝至txt即可

id ip uname pwd filename
1 192.168.1.110 testuser1 123456 1.war
2 192.168.1.111 testuser2 123456 2.war

 

引用Server類示例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Rache
# date:2020/3/27 21:07
# desc:老闆你再這樣我要刪庫了


import Server

s=Server.Server('192.168.1.207',22,'root','123459')
print(s.connect())      # 創建一個連接
print(s.open_ssh())     # 開啓ssh
print(s.open_sftp())  # 開啓sftp
# ssh發送無交互的指令
print(s.ssh_send_cmd('ls /home/yunmas'))
print('- - - - - - - - - - - -')
# 使用channel發送交互指令
print(s.open_channel())
print(s.get_prompt(expect_symbol='# '))
print(s.channel_send_cmd('df'))

參考博文:https://blog.csdn.net/phoenix339/article/details/102819764

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