应用迭代更新流程和工具分享

流程图




执行效果



脚本内容

#coding:utf-8
import os,getpass
import syslog
import time

#
#脚本名字: 生产更新脚本_v1.8
#
#备份:
#    1. 在当前目录下新建/编辑"source.txt"文件,写入需要更新的文件的绝对路径,每个文件一行,目录不需要
#       如需删除文件则在前面加"-"符号,如"- /path/file1"则表示删除此文件,修改和增加文件无需特别处理
#    2. 执行命令"python product_update.py"进行备份
#    3. 把生成的"backup.tar.gz"下载到本地,并邮件给开发人员
#
#更新:
#    1. 在当前目录下新建"update"文件夹,上传带路径的更新文件到此目录下
#    2. 执行命令"sh update.sh check"检查更新环境
#    3. 执行命令"sh update.sh"或"sh update.sh update"更新
#
#回退:
#    1. 如果更新失败,则执行命令"sh rollback.sh"回退
#
#二、功能说明
#脚本会执行如下动作:
#    1. 自动备份已存在的文件,并打包备份文档为"backup.tar.gz";
#    2. 自动生成用来更新的脚本"update.sh"
#    3. 自动生成用来回退的脚本"rollbak.sh"
#    4. 把处理结果显示在当前屏幕,并记录进syslog和当前目录的"log"文件
#    5. 如果"root"用户执行脚本需要二次确认,以防将来出现权限问题
#    6. 执行更新脚本、回退脚本需要二次确认,以防手误导致更新及回退
#    7. 执行更新脚本、回退脚本时会经过一系列严格的检查,包括文件存在性、完整性
#

def log_and_print(string,show=True,EXIT=False):
    """
    在屏幕上显示信息,并记录到syslog及日志文件中
    """
    if len(string)>0:
        if show:
            print string
        timestamp=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
        print >>LOG_FILE,"[%s] %s" % (timestamp,string)
        syslog.openlog("product_update.py")
        syslog.syslog(string)
    if EXIT:
        exit (4)
    
def get_update_list(file):
    """
    列表文件预处理,返回字典dict={"FilePath":"Operator",...}
    """
    if not os.path.exists(file):
        log_and_print("ERROR - %s 不存在!" % (file))
        exit(1)
    of=open(file,"r")
    FilePathDict={}
    FileOperator="m" #对文件执行的动作,m=修改,a=添加,d=删除
    line_number=0
    error_count=0
    for line in of.readlines():
        line_number+=1
        #规范化文件路径
        #去除行首、行尾的空白字符及换行符
        line=line.lstrip().rstrip().strip('\n')
        #删除空行
        if line=="":
            continue
        #删除“#”注释掉的行、
        if line[0]=="#":
            continue
        
        if line[0]=="-":
            FilePath=line[1:].lstrip()
            FileOperator = "d"
        else:
            FilePath = line
            FileOperator = "m"
        
        if not os.path.isabs(FilePath):
            log_and_print("ERROR - %s : 第 %s 行 : 请用绝对路径!" % (file,line_number))
            error_count+=1
        FilePathDict[FilePath]=FileOperator
    for line in FilePathDict.keys():
        if os.path.isdir(line):
            log_and_print("ERROR - %s 是一个文件夹!" % line,EXIT=True)    
    of.close()
    if error_count>0:
        exit(2)
    if len(FilePathDict)==0:
        log_and_print("ERROR - 未发现需要更新的文件!请检查 %s 文件" % LISTFILE)
        exit(4)
    return FilePathDict

def get_delete_file_list(FilePathDict={}):
    """获取需要删除的文件列表"""
    file_delete=[]
    for File,Operator in FilePathDict.items():
        if Operator == "d":
            file_delete.append(File)
    return file_delete

def get_modify_file_list(FilePathDict={}):
    """获取需要修改的文件的列表"""
    file_modify=[]
    for File,Operator in FilePathDict.items():
        if Operator == "m" and os.path.exists(File):
            file_modify.append(File)
    return file_modify

def get_add_file_list(FilePathDict={}):
    """获取新增的文件的列表"""
    file_add=[]
    for File,Operator in FilePathDict.items():
        if Operator == "m" and not os.path.exists(File):
            file_add.append(File)
    return file_add

def get_backup_file_list(FilePathDict={}):
    """获取需要备份的文件的列表"""
    file_backup=[]
    for File in FilePathDict.keys():
        if os.path.exists(File):
            file_backup.append(File)
    return file_backup
            
def create_update_script(FilePathDict={}):
    """创建更新脚本    """
    script="""#!/bin/bash\n"""
    #定义echo显示颜色
    script+=COLOR_SET
    script+="""PROGPATH=`pwd`\n"""
    mitem=0
    ditem=0
    for File,Operator in FilePathDict.items():
        target_dir=os.path.dirname(File)
        #定义更新列表
        if Operator == "d":
            script+="""delete_file_list[%s]="%s"\n""" % (ditem,File)
            ditem+=1
        else:
            script+="""source_file_list[%s]="$PROGPATH/update%s"\n""" % (mitem,File)
            script+="""target_file_list[%s]="%s"\n""" % (mitem,File)
            mitem+=1
        
    new_mkdir=[]
    mitem=0
    ditem=0
    
    for File in FilePathDict.keys():
        target_dir=os.path.dirname(File)
        #目标文件夹不存在则创建
        if not os.path.exists(target_dir):
            if target_dir not in new_mkdir:
                new_mkdir.append(target_dir)
                script+="""make_new_dir[%s]="%s"\n""" % (mitem,target_dir)
                mitem+=1

    #自定义echo功能,将信息显示到屏幕,并加上时间戳后记录到日志文件中
    script+="""
function echo_and_log {
    #屏幕显示,并记录syslog及当前目录的log文件
    echo -e $*
    logger -t product_update_$0 "$*"
    echo -e [`date +"%Y-%m-%d %H:%M:%S"`] $* >> log
}

function checkUser(){
    #用 root 账号需要二次确认
    if [ `id -u` == 0 ];then
        echo -n "你正在使用 root 账号,是否继续?[Y/N]:"
        while true
        do
            read replay
            case $replay in
                Y|y) break;;
                N|n) exit;;
                *) echo -e "$blue请输入'Y'或'N'$color_clean"
            esac
        done
    fi
}

function comfirmUpdate(){
    #更新前需要二次确认
    while true
    do
        echo -n "确认更新?[Y/N]:"
        read replay
        case $replay in
            Y|y) break;;
            N|n) exit;;
            *) echo -e "$blue请输入'Y'或'N'$color_clean"
        esac
    done
}

function checkUpdate(){
    #更新前检查相关环境
    
    return_status=0
    for ((i=0;i<${#delete_file_list[@]};i++));do
        #检查待删除的文件是否存在
        if [ ! -e ${delete_file_list[$i]} ];then
            echo_and_log "文件 ${delete_file_list[$i]} $yellow不存在!无需删除$color_clean"
        fi
        #检查待删除的文件是否有权限删除
        if [ -e ${delete_file_list[$i]} ];then
            if [ ! -w ${delete_file_list[$i]} ];then
                echo_and_log "文件 ${delete_file_list[$i]} $red无权限删除! $color_clean"
                return_status=8
            fi
        fi
    done
    
    if [ -d "$PROGPATH/update" ];then
        #检查待更新的文件是否缺失
        for ((i=0;i<${#source_file_list[@]};i++));do
            if [ ! -e ${source_file_list[$i]} ];then
                echo_and_log "文件 ${source_file_list[$i]} $red不存在!$color_clean"
                return_status=2
            fi
        done
        
        #检查待更新的文件是否过多
        for file_in_update_package in `find $PROGPATH/update -type f`;do 
            echo ${source_file_list[@]} | grep -wq $file_in_update_package
            if [ "$?" != 0 ];then
                echo_and_log "文件 $file_in_update_package $red不在更新范围内!$color_clean" 
                return_status=4
            fi
        done
        
        #检查是否有文件写入的权限
        for ((i=0;i<${#target_file_list[@]};i++));do
            if [ -e ${target_file_list[$i]} ];then
                if [ ! -w ${target_file_list[$i]} ];then
                    echo_and_log "文件 ${target_file_list[$i]} $red无写入权限! $color_clean"
                    return_status=8
                fi
#            else
#                检查文件夹权限
            fi
        done
    else
        echo -e "$red更新包未找到! $color_clean"
        return_status=1
    fi

    return $return_status
}

function update(){
    #新建文件夹
    for ((i=0;i<${#make_new_dir[@]};i++));do
        if [ ! -e ${make_new_dir[$i]} ];then
            mkdir -p ${make_new_dir[$i]} > /dev/null 2>&1
            if [ -e ${make_new_dir[$i]} ];then
                echo_and_log "新建文件夹 ${make_new_dir[$i]}\t$green[成功]$color_clean"
            else
                echo_and_log "新建文件夹 ${make_new_dir[$i]}\t$red[失败]$color_clean"
                exit
            fi
        fi
    done
    
    #开始更新文件
    for ((i=0;i<${#source_file_list[@]};i++));do
        MD5_S=`md5sum ${source_file_list[$i]}| awk '{print $1}'`
        if [ -e ${target_file_list[$i]} ];then
            MD5_D=`md5sum ${target_file_list[$i]}| awk '{print $1}'`
            if [ $MD5_S == $MD5_D ];then
                echo_and_log "更新 ${target_file_list[$i]}\t$green[成功:但文件没有发生变化!]$color_clean"
                continue
            fi
        fi
        cp -f ${source_file_list[$i]} ${target_file_list[$i]}
        if [ $? != 0 ];then
            #如果更新不成功则直接执行下一次循环,当前信息由系统错误信息替代
            continue
        fi
        
        MD5_D=`md5sum ${target_file_list[$i]}| awk '{print $1}'`
        if [ $MD5_S == $MD5_D ];then
            echo_and_log "更新 ${target_file_list[$i]}\t$green[成功]$color_clean"
        else
            echo_and_log "    $red[failed]$color_clean"
        fi
    done
    
    #开始删除文件
    for ((i=0;i<${#delete_file_list[@]};i++));do
        if [ -e ${delete_file_list[$i]} ];then
            /bin/rm -f ${delete_file_list[$i]}
            if [ ! -e ${delete_file_list[$i]} ];then
                echo_and_log "删除 ${delete_file_list[$i]} $green[成功]$color_clean"
            fi
        fi
    done

}

function usage(){
    echo "Usage: $0 [check|update]"
}


echo_and_log =====用户 `whoami` 执行  $0 $1 =====
case "$1" in
    "check")
        checkUser
        checkUpdate
        if [[ $? -eq 0 ]] ;then
            echo -e "$green环境检查OK! $color_clean "
        fi
    ;;
    "update"|"")
        checkUser
        comfirmUpdate
        checkUpdate
        if [[ $? == 0 ]] ;then
            update
        fi
    ;;
    *)
        usage
    ;;
esac
    """
    #写文件
    try:
        of=open("update.sh","w")
        of.writelines(script)
        of.close()
        log_and_print("生成更新脚本:update.sh")
        os.system("chmod a+x update.sh")
    except IOError,e:
        log_and_print(e)

def create_rollbak_script(file_in_backuped,file_be_added):
    """创建回退脚本    """
    script="""#!/bin/bash\n"""
    script+=COLOR_SET
    script+="""PROGPATH=`pwd`\n"""

    item=0
    for line in file_in_backuped:
        script+="""source_file_list[%s]=$PROGPATH/backup%s\n""" % (item,line)
        script+="""target_file_list[%s]=%s\n""" % (item,line)
        item+=1

    item=0
    for line in file_be_added:
        script+="""remove_file_list[%s]=%s\n""" % (item,line)
        item+=1
        
    script+="""
#自定义echo功能,将信息显示到屏幕,并加上时间戳后记录到日志文件中
function echo_and_log {
    echo -e $*
    logger -t product_update_$0 "$*"
    echo -e [`date +"%Y-%m-%d %H:%M:%S"`] $* >> log
}
echo_and_log =====用户 `whoami` 执行  $0 =====

#用 root 账号需要二次确认
if [ `id -u` == 0 ];then
    echo "你正在使用 root 账号,是否继续?[Y/N]:"
    while true
    do
        read replay
        case $replay in
            Y|y) break;;
            N|n) exit;;
            *) echo -e "$blue请输入'Y'或'N'$color_clean"
        esac
    done
fi

#回退前需要二次确认
while true
do
    echo -n "确认回退?[Y/N]:"
    read replay
    case $replay in
        Y|y) break;;
        N|n) exit;;
        *) echo -e "$blue请输入'Y'或'N'$color_clean"
    esac
done

#检查备份的文件是否缺失
all_file_exist=true
for ((i=0;i<${#source_file_list[@]};i++));do
    if [ ! -e ${source_file_list[$i]} ];then
        echo_and_log "文件 ${source_file_list[$i]} $red不存在!$color_clean"
        all_file_exist=false
    fi
done
#如果有丢失则退出
if ! $all_file_exist ;then
    echo_and_log "$red回退失败!$color_clean"
    exit
fi

#如有备份,则检查备份文件的完整性
if [ `cat backup.md5| wc -l`==0 ];then
    md5sum -c --status backup.md5
    if [ $? -ne 0 ]; then
        md5sum -c --quiet backup.md5
        echo_and_log "$red更新未执行退出$color_clean"
        exit
    fi
fi


#恢复被修改/删除的文件
for ((i=0;i<${#source_file_list[@]};i++));do
    cp ${source_file_list[$i]} ${target_file_list[$i]}
    if [ $? == 0 ];then
        echo_and_log "恢复 ${target_file_list[$i]}\t$green[成功]$color_clean"
    fi
done

#删除新增文件
for ((i=0;i<${#remove_file_list[@]};i++));do
    if [ ! -e ${remove_file_list[$i]} ];then
        echo_and_log "删除 ${remove_file_list[$i]}\t$red[失败:文件不存在!]$color_clean"
    else
        rm -f ${remove_file_list[$i]}
        if [ $? == 0 ];then
            echo_and_log "删除 ${remove_file_list[$i]}\t$green[成功]$color_clean"
        fi
    fi
done
"""
    #写文件
    try:
        of=open("rollbak.sh","w")
        of.writelines(script)
        of.close()
        log_and_print("生成回退脚本:rollbak.sh")
        os.system("chmod a+x rollbak.sh")
    except IOError,e:
        log_and_print(e)
    
def backup_files(file_list):
    """
    提取并备份文件
    """
    workspace=os.getcwd()
    os.system("""cat /dev/null > backup.md5""")
    log_and_print("开始备份文件...")
    for line in file_list:
        source_dir=os.path.dirname(line)
        file_name=os.path.basename(line)
        target_dir="%s%s" % (BACKUPDIR,source_dir)
        target_file="%s/%s" % (target_dir,file_name)
        #创建文件夹
        if not os.path.exists(target_dir):
            os.system("mkdir -p %s" % target_dir)
        #备份文件
        os.system("cp %s %s" % (line,target_dir))
        if os.path.exists(target_file):
            log_and_print("备份 %s [成功]" % line)
        #生成MD5校验文档
        os.system("md5sum %s >> backup.md5" % target_file)
        
    if len(file_list)==0:
        log_and_print("没有需要备份的文件!")
    else:
        tar_command="tar -czf backup.tar.gz backup"
        os.system(tar_command)
        if os.path.exists("backup.tar.gz"):
            log_and_print("打包备份文档:backup.tar.gz")
        if os.path.exists("backup.md5"):
            log_and_print("生成校验文档:backup.md5")

if __name__=="__main__":
    workspace=os.getcwd()
    #定义SHELL颜色
    global COLOR_SET
    global BACKUPDIR
    global UPDATEDIR
    global LISTFILE
    COLOR_SET="""
color_clean="\\033[0m"
blue="\\033[34m"
green="\\033[32m"
red="\\033[31m"
yellow="\\033[33m"

"""
    BACKUPDIR="backup"
    UPDATEDIR="update"
    LISTFILE="source.txt"
    if os.geteuid() == 0:
        print """
警告:你当前使用的是root用户!
"""
        i=raw_input("(C)继续 (Q)退出: ")
        if i.lower()=="c":
            pass
        else:
            exit()
    
    try:
        LOG_FILE=open("log","a")
    except IOError,e:
        print(e)
    
    log_and_print("=====用户 %s 执行 %s =====" % (getpass.getuser(),os.path.basename(__file__)))
    if not os.path.exists(workspace):
        os.system("mkdir -p %s" % workspace)
    FilePathDict=get_update_list(LISTFILE)
    FileNeedBeBackuped=get_backup_file_list(FilePathDict)
    backup_files(FileNeedBeBackuped)
    create_rollbak_script(FileNeedBeBackuped,get_add_file_list(FilePathDict))
    create_update_script(FilePathDict)


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页