基于Python3+PyQt5的自动替换部署工具介绍(二)

上一篇文章主要这款工具的环境准备、Qt Designer的基本使用,以及工具界面的生成(备注:今天抽空又把界面优化了一下,增加了日志显示),今天主要介绍后台具体实现逻辑部分。

基于Python3+PyQt5的自动替换部署工具介绍(二)_第1张图片
主界面.png

paramiko的基本使用

之前的一篇文章已经介绍过了paramiko的相关基础操作:
主要包括:

1.连接服务器
def trans_connect(host,username,password):
    try:
        trans = paramiko.Transport((host,22))
        trans.connect(username=username,password=password)
    except Exception,e:
        print e
    return trans

def ssh_connect(host,username,password):
    try:
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host,22,username,password)
    except Exception ,e:
        print e
    return ssh_client
2.一些基本命令的实现
    cmd = "cd /home ;ls ;pwd" 
    arg = 'get_pty=True'
    ssh_client = ssh_connect(host, username, password)
    stdin, stdout, stderr = ssh_exec_cmd(ssh_client, cmd, arg)
    for line in stdout:
        print line.strip('\n')
    time.sleep(2)
    ssh_close(ssh_client)
3.上传下载文件至服务器
def trans_web(trans,remotepath,localpath):
    sftp = paramiko.SFTPClient.from_transport(trans)
    sftp.put(localpath,remotepath)
    trans.close()

下载文件,只需切换localpath和remotepath的位置即可

内部逻辑实现

上一篇说到了界面与逻辑分离的规则,所有的界面实现都集中在FirstMainWin.py 这个文件中实现,而所有后台逻辑实现都集中在Change.py中实现,保证页面发生改变时不需要大概后台逻辑,同样改动后台逻辑时也不会对前台页面造成影响。
首先缕一下在xshell中手动部署替换文件的顺序:
1.连接服务器
2.cd 到文件路径,备份该文件
3.上传文件
4.重启服务器(非必要操作)
同样的顺序,在用这个工具的时候,也是按照连接服务--备份--上传文件--重启的顺序;
即:在点开始部署这个pushbutton时要激活上述所有步骤:
首先定义这些基础的步骤

连接服务

先介绍一下QComboBox(下拉列表框)的一些常用方法和常用信号:

(1)addItems

在QComboBox的添加一个下拉选项。

(2)count

返回列表项总数。

(3)currentIndex

当前显示的列表项序号。

(4)currentText

返回当前显示的文本。

常用信号:
(1)Activated

当用户选中一个下拉框时发射该信号

(2)currentIndexChanged

当下拉选项的索引发生变化时发射该信号

(3)highlighted

当选中一个已经选中的下拉选项时,发射该信号

class Main(QMainWindow,Ui_Form):
    def __init__(self,parent=None):
        super(Main,self).__init__(parent)
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.setWindowTitle('Waiqin365-DATT-V1.0.0')
        self.password = 'xxxxxxxx'
        self.username='xxxx'

        self.ui.environment.activated.connect(self.printtomcat)
        self.ui.filename.editingFinished.connect(self.filename)
        self.ui.filenumber.activated.connect(self.filenumber)
        self.ui.filetype.activated.connect(self.filetype)
        self.ui.restart.activated.connect(self.needrestart)
        self.ui.start.clicked.connect(self.start)

def printtomcat(self):
        '''
        根据选择的环境返回Index值,供下面connect使用
        1   231
        2   233
        '''
        print(self.ui.environment.currentText())
        tomcat = self.ui.environment.currentIndex()
        self.ui.log.setPlainText('部署的环境为:'+self.ui.environment.currentText())
        return tomcat

def connect(self,Index):
        '''
        根据传入的Index连接服务器
        :param Index: 1  231  2  233
        :return: ssh
        '''
        if Index == 1:
            try:
                ssh = paramiko.SSHClient()
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                ssh.connect('172.31.3.231',22,self.username, self.password)
            except Exception:
                print (Exception)
            return ssh
        elif Index == 2:
            try:
                ssh = paramiko.SSHClient()
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                ssh.connect('172.31.3.233',22,self.username, self.password)
            except Exception:
                print (Exception)
            return ssh

替换环境这个下拉框的object name(在设计界面的时候可以自定义)为environment

self.ui.environment.activated.connect(self.printtomcat)

当选中一个下拉选项时,激活printtomcat这段代码,并返回选项的Index,供下面连接服务器connect部分使用
同样的道理:
文件类型的两个下拉选项,是否重启的两个下拉选项,都是选中一个选项时,激活相应的操作

返回文件数,是单个文件还是压缩文件

self.ui.filenumber.activated.connect(self.filenumber)

    def filenumber(self):
        '''
        根据选择的文件数量:单个文件还是web.zip,返回Index供下面onefile和zip使用
        1  单个文件
        2  web.zip
        '''
        print(self.ui.filenumber.currentIndex())
        filenumber = self.ui.filenumber.currentIndex()
        self.ui.log.setPlainText('文件类型为:'+self.ui.filenumber.currentText())
        return filenumber
返回文件类型是平台文件还是应用文件

self.ui.filetype.activated.connect(self.filetype)

    def filetype(self):
        '''
        根据选择的文件类型:平台文件还是应用文件,自动加上前缀 /home/...web/WEB-INFO/class  还是 /home/...web/
        1  应用文件
        2  平台文件
        '''
        print(self.ui.filetype.currentIndex())
        filetype = self.ui.filetype.currentIndex()
        self.ui.log.setPlainText('文件为:'+self.ui.filetype.currentText())
        return filetype
是否需要重启

self.ui.restart.activated.connect(self.needrestart)

    def needrestart(self):
        '''
        根据所选,返回Index,供下面使用是否需要重启
        1 需要
        2 不需要
        '''
        need = self.ui.restart.currentIndex()
        print(need)
        return need

备份替换文件并上传文件

如果文件是单个文件,根据传入的Index判断是平台文件还是应用文件,在返回的文件路径前自动补全地址
/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes 或者 /home/iorder_appsvr/iorder_appsvr/web

返回文件名
    def filename(self):
        '''
        返回填写的filename
        '''
        print(self.ui.filename.text())
        filename = self.ui.filename.text()
        self.ui.log.setPlainText('文件名为:'+self.ui.filename.text())
        return filename

    def localfile(self):
        '''
        返回local,即文件所在路径
        :return:
        '''
        print(self.ui.filename.text())
        filename = self.ui.filename.text()
        localfile = 'D:/file/'+ filename
        print(localfile)
        return localfile
    def path(self):
        '''
        返回填写的filepath 文件路径
        '''
        print(self.ui.filepath.toPlainText())
        filepath = self.ui.filepath.toPlainText()
        return filepath

filename和filepath分别是QLineEdit和QTextEdit

QLineEdit常用方法:
(1)clear

清除文本内容

(2)setText

设置文本内容

(3)Text

返回文本框内容

(4)selectAll

全选

QTextEdit常用方法:
(1)setPlainText

设置多行文本框内容

(2)toPlainText

返回多行文本框内容

其中trans主要用来上传文件,根据Index来连接对应的服务器
onefile这个方法,主要用来替换单个文件,根据传入的Index,type连接对应的服务器,并根据类型拼接文件地址,然后执行相应的命令,备份文件,上传文件等操作
zip这个方法,主要用来替换web.zip,只需要一个参数Index,连接对应的服务器即可以,也是类似的操作:上传文件,解压,复制。

def trans(self,Index,localpath,remotepath):
        '''
        根据传入的Index连接服务器并上传文件
        :param Index:
        :return:
        '''
        if Index == 1:
            try:
                trans = paramiko.Transport(('172.31.3.231',22))
                trans.connect(username=self.username,password=self.password)
            except Exception as e:
                print (e)

            sftp = paramiko.SFTPClient.from_transport(trans)
            sftp.put(localpath,remotepath)
            time.sleep(2)
            trans.close()

        elif Index == 2 :
            try:
                trans = paramiko.Transport(('172.31.3.233',22))
                trans.connect(username=self.username,password=self.password)
            except Exception as e:
                print (e)

            sftp = paramiko.SFTPClient.from_transport(trans)
            sftp.put(localpath,remotepath)
            time.sleep(2)
            trans.close()


    def onefile(self,Index,type):
        '''
        一个文件,根据传入的type判断平台文件还是应用文件,拼接完整的路径
        :param type: 1 应用文件  2  平台文件
        :param type: Index  1  连接231  2  连接233
        :return:
        '''
        if type == 1:
            remotepath = '/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes' + str(self.path())
            self.ui.log.setPlainText('文件路径为:'+ remotepath)
            filename = self.filename()
            self.localfile()
            bak = filename + time.strftime("%y%m%d") + 'bak'
            cmd = 'cd  {0};mv {1}  {2}'.format(remotepath,self.filename(),bak)
            #备份并上传替换文件
            print(cmd)
            self.ui.log.setPlainText('备份替换文件')
            # 根据Index 连接对应的环境
            ssh = self.connect(Index)
            stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)
            for line in stdout:
                print (line.strip('\n'))
            ssh.close()

            remotefile = '/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes' + str(self.path())+str(self.filename())
            self.trans(Index,self.localfile(),remotefile)
            self.ui.log.setPlainText('文件上传成功')
            return cmd,filename,remotepath

        elif type == 2:
            remotepath = '/home/iorder_appsvr/iorder_appsvr/web' + str(self.path())
            self.ui.log.setPlainText('文件路径为:'+ remotepath)
            filename = self.filename()
            bak = filename + time.strftime("%y%m%d") + 'bak'
            cmd = 'cd  {0} ;mv {1}  {2}'.format(remotepath,self.filename(),bak)
            print(cmd)
            self.ui.log.setPlainText('备份替换文件')

            ssh = self.connect(Index)
            stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)

            for line in stdout:
                print (line.strip('\n'))
            ssh.close()

            remotefile = '/home/iorder_appsvr/iorder_appsvr/web' + str(self.path())+str(self.filename())
            self.trans(Index,self.localfile(),remotefile)
            self.ui.log.setPlainText('文件上传成功')
            return cmd,filename,remotepath


    def zip(self,Index):
        '''
        web.zip包直接复制到opt后解压后复制
        Index  用来判断231 还是 233
        :return:
        '''
        cmd = 'cd /opt;rm -rf web;mkdir web;ls'
        # cd 到opt下创建新的web目录
        ssh = self.connect(Index)
        stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)
        for line in stdout:
                print (line.strip('\n'))
        ssh.close()

        #上传文件至opt/web 并解压
        remotepath = '/opt/web/{}'.format(self.filename())
        localpath = r'D:/file/{}'.format(self.filename())
        print(remotepath,localpath)
        self.trans(Index,localpath,remotepath)
        self.ui.log.setPlainText('压缩包文件上传成功')


        cmd1 = 'cd /opt/web;ls;unzip {};rm -rf  {}'.format(self.filename(),self.filename())
        ssh = self.connect(Index)
        stdin, stdout, stderr = ssh.exec_command(cmd1,get_pty=True)
        for line in stdout:
                print (line.strip('\n'))
        time.sleep(2)
        #复制解压后的文件
        cmd2 = '\cp -Rf /opt/web/*    /home/iorder_appsvr/iorder_appsvr/'
        stdin, stdout, stderr = ssh.exec_command(cmd2,get_pty=True)
        for line in stdout:
                print (line.strip('\n'))
        ssh.close()
        self.ui.log.setPlainText('复制zip替换文件成功')

开始部署

上面一切准备工作就绪后,点击开始部署就Ok了

基于Python3+PyQt5的自动替换部署工具介绍(二)_第2张图片
开始部署.png

部署部分的逻辑如下:
根据上面printtomcat这个方法返回的Index,判断是对哪台服务器进行部署
根据filenumber这个方法,判断是单个文件还是web.zip,并对应的使用onefile这个方法还是zip这个方法
根据needrestart这个方法,判断是否需要重启

 def start(self):
        '''
        开始部署  1  部署231   2  部署233
        :return:
        '''
        if self.printtomcat() == 1:
            print('部署环境为:'+ self.ui.environment.currentText())

            # 部署单个文件
            if self.filenumber() == 1:
                self.onefile(1,self.filetype())

            #部署web.zip
            elif self.filenumber() == 2:
                #连接231 部署web.zip
                self.zip(1)
                # print('222')

            #判断是否需要重启
            if self.needrestart() == 1:
                ssh = self.connect(1)
                stdin, stdout, stderr = ssh.exec_command('service  tomcat_iorder_appsvr  restart',get_pty=False)
                for line in stdout:
                    print (line.strip('\n'))
                time.sleep(5)
                ssh.close()
            elif self.needrestart() == 2:
                time.sleep(2)

        elif self.printtomcat() == 2:
            print('部署环境为:'+ self.ui.environment.currentText())

            # 部署单个文件
            if self.filenumber() == 1:
                self.onefile(2,self.filetype())

            # 部署web.zip
            elif self.filenumber()== 2:
                #连接231 部署web.zip
                # print('222 222')
                self.zip(2)

            # 判断是否需要重启
            if self.needrestart() == 1:
                ssh = self.connect(2)
                stdin, stdout, stderr = ssh.exec_command('service  tomcat_iorder_appsvr  restart',get_pty=False)
                for line in stdout:
                    print (line.strip('\n'))
                time.sleep(2)
                ssh.close()
                self.ui.log.setPlainText('部署完成,服务器重启中,请稍候')
            elif self.needrestart() == 2:
                time.sleep(2)
                self.ui.log.setPlainText('部署完成')

上面就是后台逻辑实现部分。
既然这是一个窗口工具,那么如何在其他电脑上运行呢,只需要一步;

打包成exe文件

打包生成EXE文件只需要两个步骤

1.安装PyInstaller
pip install PyInstaller

安装成功后,可以在python/Scripts下找到

基于Python3+PyQt5的自动替换部署工具介绍(二)_第3张图片
PyInstaller.png
2.打包
pyinstaller [opts] xxxxx.py

可选参数有

  • -F,-onefile 打包成一个EXE文件
  • -D,-onedir,创建一个目录,包含EXE文件,但会依赖很多文件
  • -c,-console,-nowindowed,使用控制台,五窗口
  • -w,-windowed,noconsole,使用窗口,无控制台
    打开命令行,进入到需要打包的.py文件目录,运行下面的命令:
pyinstaller -F -w  xxxxx.py

完成后会在同目录的dist文件夹中生成一个同名的.exe文件,双击这个exe文件,运行效果和直接使用编辑器运行脚本效果一样

基于Python3+PyQt5的自动替换部署工具介绍(二)_第4张图片
目录.png
基于Python3+PyQt5的自动替换部署工具介绍(二)_第5张图片
exe.png

在其他电脑上使用时,直接将dist目录拷贝过去即可,我这边加了platforms是因为在其他电脑运行报错,paltforms文件夹来自于

C:\Python34\Lib\site-packages\PyQt5\plugins\platforms

你可能感兴趣的:(基于Python3+PyQt5的自动替换部署工具介绍(二))