造个轮子,用python写的web项目自动部署系统

虽然已经有了Jenkis等强大的持续集成系统,但仍阻挡不了我对造轮子的热爱。

适用框架:Thinkphp,正增加对Laravel的支持

功能:将项目代码进行版本控制,便于保存旧版本,快速切换不同版本。

优点:无需安装!配置超简单!上线快!

要求:

1.备份你的线上代码,以防万一

2.将新的项目目录使用zip压缩

3.第一次使用,需要把项目根目录设置为软链接到某空目录,此空目录权限需和项目目录应该有的访问权限相同,随后程序会自动将项目根目录软链接到新的项目路径

4.第3步设置完成后,直接运行以下代码,根据提示配置即可。

流程概述:

1.解压项目zip文件,以project.zip举例

2.将解压后的项目目录(project)以版本号的形式更改名字(project.201710011300),移动到版本库(path_to_appstore/project/project.201710011300)

3.对project.201710011300进行处理,包括更改owner,清除误上传的缓存(如有)

4.将项目根目录(path_to_app_base)软链接到该版本号目录(ln  -sfn  path_to_appstore/project/project.201710011300   path_to_app_base)

5.将该版本的特定目录(如用户上传文件),软链接到指定线上目录((ln  -sfn  path_to_appstore/project/project.201710011300/special_path   path_to_project_special)

#!/usr/bin/python
# -*- coding: UTF-8 -*- 
import os,time,subprocess,shutil

defaultAppName='project.haha.com' #项目名
defaultStaticFolderName='Uploads' #用户上传静态文件夹默认名
appOwner='www:www' #默认文件所有者
defaultAppParentPath='/data/wwwroot/' #项目所在目录
defaultAppPath=defaultAppParentPath+defaultAppName #项目路径
defaultAppStaticPath=defaultAppParentPath+defaultAppName+'.'+defaultStaticFolderName #项目的特殊资源目录
newFileDir='' #上传文件所在路径
defaultNewFile='upload.zip' #上传文件名
newFile='' #根据用户输入判断
factoryDir=defaultAppParentPath+'/factoryDir' #存放上传的zip文件
newFileUnzipedWorkDir=factoryDir+'/'+'unziped' #在此目录中对上传文件进行解压等操作,以防万一
appBackDir=defaultAppParentPath+'/appback/'+defaultAppName+'/' #项目的备份文件夹
appStoreParent=defaultAppParentPath+'appstore/' #项目版本库的上级文件夹
appStore=appStoreParent+defaultAppName+'/' #项目版本库
appCleanRuntimeDir=['/Runtime/Cache','/Runtime/Data','/Runtime/Temp'] #需要清除的缓存路径

timeSign=time.strftime("%a-%b-%d-%H-%M-%S-%Y", time.localtime()) #随后文件中使用的时间标记(如版本号)

#确定上传文件所在目录并验证 
def judgePath():
    global newFileDir,defaultAppPath
    print "压缩包默认已上传至"+defaultAppPath
    newFileDir=raw_input("如果是以上目录,请press enter,如果不是,请输入存放目录:")
    if newFileDir=='':
        newFileDir=defaultAppPath
    if os.path.isdir(newFileDir)==False:
        print "亲,目录并不存在啊 ( >﹏<。)"
        return judgePath()
    if os.access(newFileDir, os.W_OK)==False:
        print '亲,我木有权限进入:'+newFileDir+' ( >﹏<。)'
        return judgePath()
    else:
        return os.path.abspath(newFileDir)+'/' # abspath:返回path规范化的绝对路径

#确定上传的文件名并验证
def judegeNewFile():
    global newFile
    newFile=raw_input("默认zip文件是"+defaultNewFile+",如果是,请press enter,如果不是,请输入文件名:")
    if newFile=='':
        newFile=defaultNewFile
    if os.path.exists(newFileDir+newFile)==False:
        print '亲,'+newFileDir+newFile+'并不存在啊 ( >﹏<。)'
        return judegeNewFile()
    if os.access(newFileDir+newFile, os.W_OK)==False:
        print '亲,我木有写权限:'+newFile+' ( >﹏<。)'
        return judegeNewFile()
    else:
        return newFile
#start...
#删除zip文件工作目录
if os.path.isdir(newFileUnzipedWorkDir)==True:
    shutil.rmtree(newFileUnzipedWorkDir)
os.makedirs(newFileUnzipedWorkDir)


print '***注意***,默认操作的项目是:'+defaultAppName
#获取上传文件所在的路径
newFileDir= judgePath()
#获取上传文件的名字
newFile= judegeNewFile()

filePath=newFileDir+newFile #上传文件路径

#移动文件到处理目录并改名
print ''+newFile+'移动至'+factoryDir+'进行处理...'
newFilePath=factoryDir+'/'+newFile
shutil.move(filePath,newFilePath) 
print "已将zip文件移动至"+newFileUnzipedWorkDir

#解压文件
print '开始解压文件...'
pUnzip=subprocess.Popen(["unzip",newFilePath,'-d',newFileUnzipedWorkDir], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) 

#注意,如果使用stderr但不使用stdin,那unzip时是不行的,为什么?万能的网友请告诉我
'''Popen.communicate(input=None)
Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child.

communicate() returns a tuple (stdoutdata, stderrdata).
  
Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.
'''

tupleUnzip = pUnzip.communicate('A')
if tupleUnzip[-1]=='':
    print "解压文件完毕"
else:
    print "解压出错了.错误为:"+tupleUnzip[-1]
    exit()
unzipedFileList = os.listdir(newFileUnzipedWorkDir)
if len(unzipedFileList)>1:
    print "解压后发现上传文件并不是一个文件夹,目前功能很low,请压缩整个文件夹再上传"
    exit()
afterUnzipedFindThisFolder=unzipedFileList[0];#解压完后,发现的文件夹名字


#把新项目文件放到版本库中
thisVerName=defaultAppName+"."+timeSign
#在压缩文件处理车间处理过的文件放到appstore中并重命名
'''
tips
mv用法 格式:mv dir1 dir2 如果目录dir2不存在,将目录dir1改名为dir2;否则,将dir1移动到dir2中。 ''' print "准备把处理好的解压文件放到"+appStore+'/'+thisVerName shutil.move(newFileUnzipedWorkDir+'/'+afterUnzipedFindThisFolder,appStore+'/'+thisVerName) print "已将处理好的解压文件放到"+appStore+'/'+thisVerName #删除缓存 for dir in appCleanRuntimeDir: absDir=appStore+'/'+thisVerName+'/'+dir if (os.path.exists(absDir)): #清理 try: shutil.rmtree(absDir) print "已删除"+absDir except OSError,e: print "删除失败!!!"+str(e) else: print '缓存目录'+appStore+'/'+dir+'不存在,无法清理' #更改文件所有者 pChown=subprocess.Popen(["chown",appOwner,appStore+'/'+thisVerName,'-R'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) tupleChown = pChown.communicate(); if tupleChown[-1]=='': print "已将文件夹的所有者改为:"+appOwner else: print "更改所有者失败.错误为:"+tupleChown[-1] exit() #创建软连接 print "开始创建软链接"
#1.创建整个项目的软链接
pLn=subprocess.Popen(["ln",'-sfn',appStore+'/'+thisVerName,defaultAppPath], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) tupleLn = pLn.communicate() if tupleLn[-1]=='': print "已将"+defaultAppName+'软链接到:'+appStore+'/'+thisVerName else: print "创建软链接错误.错误为:"+tupleChown[-1] exit() #2.创建特殊目录软连接 shutil.move(appStore+'/'+thisVerName+"/"+defaultStaticFolderName,appStore+'/'+thisVerName+"/"+defaultStaticFolderName+'.bak') if (os.path.exists(defaultAppStaticPath)==False): print '发现'+defaultAppStaticPath+'不存在,准备复制' os.makedirs(defaultAppStaticPath) #TODO:这里是异步,因此需要等待一秒,有待改进 time.sleep(1) print '准备复制的文件夹为'+appStore+'/'+thisVerName+"/"+defaultStaticFolderName+'.bak' print '最终目录为'+defaultAppStaticPath #由于subprocess中无法识别星号*,所以暂时放弃了 #cp -R ./Public.bak/* /webserver/wwwroot/Public #pCpStatic=subprocess.Popen(["cp",'-R',appStore+'/'+thisVerName+"/"+defaultStaticFolderName+'.bak'+'/*',defaultAppStaticPath], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) #tupleCpStatic = pCpStatic.communicate() #if tupleCpStatic[-1]=='': # print "已将"+appStore+'/'+thisVerName+"/"+defaultStaticFolderName+'复制到链接到:'+defaultAppStaticPath #else: # print "复制错误.错误为:"+tupleCpStatic[-1] # exit() os.popen('cp -R '+appStore+'/'+thisVerName+"/"+defaultStaticFolderName+'.bak'+'/* '+defaultAppStaticPath) pLnStatic=subprocess.Popen(["ln",'-sfn',defaultAppStaticPath,appStore+'/'+thisVerName+"/"+defaultStaticFolderName], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) tupleLnStatic = pLnStatic.communicate() if tupleLnStatic[-1]=='': print "已将"+defaultAppStaticPath+'软链接到:'+appStore+'/'+thisVerName+"/"+defaultStaticFolderName else: print "特殊目录创建软链接错误.错误为:"+tupleLnStatic[-1] exit() ''' TODO: 1.严格限制各个文件夹权限,满足访问的基础上,尽可能最小化权限 2.解压后确定是根目录 1.当前项目配置中的app_debug检测 '''

 

posted @ 2018-01-18 10:36  toDoYourBest  阅读(...)  评论(... 编辑 收藏