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

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

主界面.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了

开始部署.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下找到

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文件,运行效果和直接使用编辑器运行脚本效果一样

目录.png
exe.png

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容