流程图
执行效果
脚本内容
#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)