python 文件自動配置&部署腳本

背景:

一個項目有時候要上傳到測試機,有時候要上傳到正式機。

有一次因爲配置錯了環境,白忙活了一整天,決定這種事還是交給腳本來實現吧。


思路:

1、先用正則表達式把環境變量給抓出來,然後修改成要上傳服務器的環境變量後寫回文件。

2、拋棄一些子目錄如log,venv等(雖然不要裏面的內容,但要把它們本身這個空目錄加入tar),開始tar打包。

3、計算打包後文件的md5。

4、用SFTP協議傳到服務器,具體用python的第三方lib:paramiko,博主自己寫了個類封裝。

5、計算傳輸後文件的md5,比對兩者md5,如果相同則說明數據完整,可以繼續,否則exit。

6、用paramiko中的exec_command()可以操控服務器執行我們的cmd。

7、之後做些收尾工作,比如複製好virtualenv、重啓supervisor呀之類的。


# coding=utf-8
import os
import shutil
import time
import re
import tarfile
import hashlib

import paramiko

src_root = 'g:\\projects\\myApp\\'
des_root = 'g:\\projects\\myapp_helper\\'
linux_root = '/home/www/myapp_uploads/'

today = time.strftime('%Y-%m-%d-%H-%M', time.localtime(time.time()))
des_target = os.path.join(des_root, 'test_myapp%s\\' % today)
print u'目標根目錄:', des_target


class MySSH(object):
    def __init__(self, host, username, password):
        self.host = host
        self.username = username
        self.password = password
        self.ssh_fd = None
        self.sftp_fd = None

        self.ssh_connect()
        self.sftp_open()

    def ssh_connect(self):
        try:
            print u'連接SSH...'
            self.ssh_fd = paramiko.SSHClient()
            self.ssh_fd.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.ssh_fd.connect(self.host, 22, username=self.username, password=self.password)

            stdin, stdout, stderr = self.ssh_fd.exec_command("mkdir %s" % linux_root)
            print stdout.readlines()
            print u'連接SSH 成功...'
        except Exception, ex:
            print 'ssh %s@%s: %s' % (self.username, self.host, ex)
            exit()

    def sftp_open(self):
        print u'打開SFTP 成功...'
        self.sftp_fd = self.ssh_fd.open_sftp()

    def sftp_put(self, from_path, to_path):
        '''
            上傳文件到遠程服務器
        '''
        return self.sftp_fd.put(from_path, to_path)

    def sftp_get(self, from_path, to_path):
        '''
            從遠程服務器下載文件
        '''
        return self.sftp_fd.get(from_path, to_path)

    def exe(self, cmd):
        '''
            讓遠程服務器執行cmd
        '''
        stdin, stdout, stderr = self.ssh_fd.exec_command(cmd)
        print u'執行%s:' % cmd
        print stderr.readlines()
        if cmd.find('md5') == 0:
            return stdout.readline()

    def close(self):
        self.sftp_fd.close()
        self.ssh_fd.close()
        print u'關閉SSH連接...成功'

    def __del__(self):
        self.close()


def copy_project():
    '''
        1、先把src項目拷貝到des下
        2、除去某些子目錄:discard_dirs = ['myproto', 'log', 'myapp_env', 'test', 'tmp', 'upload', '.idea', '.git']
        3、除去某些不需要的文件: .gitignore, README.md
        4、遇到配置文件,把環境變量改成測試環境...api_status = 2
    '''
    if not os.path.exists(des_target):
        print u'創建根目錄:', des_target
        os.makedirs(des_target)

    discard_dirs = ['myproto', 'log', 'myapp_env', 'test', 'tmp', 'upload', '.idea', '.git', 'uploads']

    for root, dirs, files in os.walk(src_root, topdown=False):
        t_root = root.replace(src_root, des_target)

        for dir in dirs:
            t_dir = os.path.join(t_root, dir)
            # print t_dir
            if len(list(set(t_dir.split('\\')).intersection(set(discard_dirs)))) > 0:
                continue

            if not os.path.exists(t_dir):
                print u'目錄安全,創建...',t_dir
                os.makedirs(t_dir)
            if not os.listdir(t_dir):
                print u'目錄爲空,刪除...',t_dir
                os.removedirs(t_dir)

        for file in files:
            if file.endswith('.pyc'):
                continue
            if file in ['.gitignore', 'README.md']:
                continue

            src_file = os.path.join(root, file)
            des_file = os.path.join(t_root, file)
            if len(list(set(src_file.split('\\')).intersection(set(discard_dirs)))) > 0:
                continue

            des_folder = des_file[0: -len(des_file.split('\\')[-1])]
            if not os.path.exists(des_folder):
                print u'目錄安全,創建...',des_folder
                os.makedirs(des_folder)

            # print src_file, ' > ', des_file
            if file != 'settings.py':
                shutil.copy(src_file, des_file)
            else:
                out = open(des_file, 'w')
                datas = open(src_file, 'r')

                pattern = re.compile(r'api_status\s*=\s*\d')
                for line in datas.readlines():
                    if pattern.match(line):
                        line = 'api_status = 2\n'
                    out.write(line)
                datas.close()
                out.close()
    # 補全
    print des_target+'log'
    os.mkdir(des_target+'log')

    os.mkdir(des_target+'uploads')


def tar_project(dir_path):
    '''
        1、用tarfile來打包,按照這種方法打包是會忽略空目錄的。
        2、在tar中加入空目錄。
        dir_path =  g:\projects\chanzai_helper\test_chanzai2016-11-22-11-09
        return filename, filepath
    '''
    # 創建壓縮包名
    tar_name = "%s.tar.gz" % dir_path.split('\\')[-2]
    tar_fullpath = des_root+tar_name
    print u'壓縮包名字:', tar_fullpath

    tar = tarfile.open(tar_fullpath, "w:gz")

    # 創建壓縮包
    for root, dirs, files in os.walk(dir_path):
        for file in files:
            fullpath = os.path.join(root, file)
            # print u'壓縮:', fullpath
            filename = fullpath.replace(des_target, '')
            tar.add(fullpath, arcname=filename)
    tar.add(des_target+'log', arcname="log/")
    tar.add(des_target+'uploads', arcname="uploads/")
    tar.close()
    return tar_name, tar_fullpath


def CalcMD5(filepath):
    '''
        獲取文件md5, 用來檢驗文件傳輸完整性
    '''
    with open(filepath, 'rb') as f:
        md5obj = hashlib.md5()
        md5obj.update(f.read())
        hash = md5obj.hexdigest()
        return hash

copy_project()
filename, filepath = tar_project(des_target)
from_md5 = CalcMD5(filepath)
print u'壓縮文件md5 = %s' % from_md5

# 刪除本地的文件夾
shutil.rmtree(des_target)


ssh = MySSH('xxx.xx.xx.xxx', 'your_name', 'your_pass')
print u'傳輸:', filename
ssh.sftp_put(filepath, linux_root + filename)

# 獲取linux那邊文件的md5
cmd = "md5sum %s%s|cut -d ' ' -f1" % (linux_root, filename)
to_md5 = ssh.exe(cmd)
print u'傳輸過去的文件md5 = %s' % to_md5


to_md5 = to_md5.strip('\n')
if from_md5 != to_md5:
    print len(from_md5), len(to_md5)
    print u'ERROR: 文件傳輸不完整...'
    exit()
else:
    print u'OK: 文件傳輸完整!'

    # 解壓縮 tar 包
    folder = filename.split('.')[0]
    cmd = 'mkdir %s%s;cd %s%s;tar -zxvf %s%s' % (linux_root, folder, linux_root, folder, linux_root, filename)
    ssh.exe(cmd)

    # 刪除linux - tar包
    cmd = 'cd %s; rm -rf %s%s' % (linux_root, linux_root, filename)
    err = ssh.exe(cmd)
    if err:
        print err
        exit()

    # 刪除/home/www/myAppbak
    cmd = 'cd /home/www; rm -rf /home/www/myAppbak'
    err = ssh.exe(cmd)
    if err:
        print err
        exit()

    # 交換當前工作目錄
    cmd = 'mv /home/www/myApp /home/www/myAppbak'
    err = ssh.exe(cmd)
    if err:
        print err
        exit()

    cmd = 'mv %s%s /home/www/myApp' % (linux_root, folder)
    err = ssh.exe(cmd)
    if err:
        print err
        exit()

    # 把 虛擬環境複製過去
    cmd = 'cp -r /home/www/myAppbak/myapp_env /home/www/myApp/myapp_env'
    err = ssh.exe(cmd)
    if err:
        print err
        exit()

    # 在supervisor中重啓
    cmd = 'supervisorctl restart myapp'
    err = ssh.exe(cmd)
    if err:
        print err
        exit()


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