从零开始自动部署Django项目(二):使用Python编写Git Hooks

引言

在上一篇从零开始自动部署Django项目(一):开发配置与生产配置,已经给出了通过环境变量来实现不同配置选择的解决方案。既然是环境变量,那就可以通过shell脚本或者python脚本来实现。
接下来,我将通过使用python编写的Git Hooks来实现在向生产服务Git推送的时候完成自动部署。
不太了解Git Hooks的同学可以参考我这篇文章: 服务端自动部署静态项目的几种方法。

准备

首先明确Git Hooks要完成哪些任务:

  • 判断python debug server在centos系统服务器上是否运行在指定端口,如果在运行则杀掉该进程。
  • git pull 前端的release分支。
  • git pull 后端的release分支并通过nohup重启python debug server。

明确要完成的任务之后,还有几点利用Python编写Git Hooks要注意的地方:

  • 由于Git Hooks是支持shell,python,ruby脚本的,因此在post-receive脚本的第一行应该声明使用哪个解释器(interpreter):

    
    #!/usr/bin/env python
    

    stack overflow传送门:Why do people write #!/usr/bin/env python on the first line of a Python script?

  • stack overflow传送门:How do I “cd” in Python?
  • 如何在Python中执行shell命令呢?我使用的是Python中的subprocess模块,这个模块是为了取代诸如os.system,os.popen,commands模块,其中commands模块在Python3.0中已经被废弃了。同时使用subprocess模块也是Python3 cookbook所推荐的。
    subprocess官方文档传送门: subprocess — Subprocess management。
    Python Cookbook传送门:执行外部命令并获取它的输出。

杀掉Python debug server进程

如果server 进程运行在9999端口,使用linux提供的命令如何杀掉进程呢?
首先通过lsof命令观察9999端口是否有进程运行,如果有则获取该进程的pid,得到以后通过kill命令杀掉该进程。因此,我们要在Python中完成对lsof命令输出的截取。
其中,subprocess.call()调用会返回命令执行的状态,1表示错误,0表示正常执行。

import subprocess
import os
import signal

# kill the python debug server
server_status = subprocess.call(lsof_command, shell=True)

if server_status == 0:
    server_res_bytes = subprocess.check_output("lost -i:9999 | tail -n 1", shell=True)
    server_res = server_res_bytes.split()
    python_server_pid = int(server_res[1])
    try:
        os.kill(python_server_pid, signal.SIGKILL)
    except OSError:
        pass

更新前端和后端分支

在确认Python debug server已经被杀掉的基础上,可以进行对分支的pull和checkout了。

# checkout the FrontEnd branch
os.chdir(frontend_path)
subprocess.call("git fetch --all", shell=True)
subprocess.call("git reset --hard origin/" + fronend_branch, shell=True)

# checkout the BackEnd branch
os.chdir(backend_path)
subprocess.call("git fetch --all", shell=True)
subprocess.call("git reset --hard origin/" + backend_branch, shell=True)

OK,但是在这里我遇到了一个神秘的错误:

remote: fatal: Not a git repository: '.'

这是为什么呢?原来Git Hooks在执行的时候会设置$GIT_DIR变量为.而并非.git,这会导致Git会去寻找./HEAD,而并非.git/HEAD,因此在hooks脚本一开始需要执行unset GIT_DIR或者把GIT_DIR设置成.git。
在一开始的时候,我直接调用了subprocess.call(‘unset GIT_DIR’, shell=True),然而并不起作用,这是因为subprocess只是在子进程中unset了变量,而并非当前的Python进程,因此我要通过os.environ影响当前Python进程衍生的子进程。

# didn't work
subprocess.call('unset GIT_DIR', shell=True)

# work fine
os.unsetenv('GIT_DIR')

stackoveflow传送门:Git checkout in post-receive hook: “Not a git repository ‘.’”
以及Git如果在Git仓库中Git_DIR就会被设置成.git,而非git仓库的时候就是.,而Git命令在执行的时候会默认去寻找.git目录。传送门:Receiving “fatal: Not a git repository” when attempting to remote add a Git repo

重启Python debug server

最后只需要通过manage.py 重启debug server就可以了,但在这之前需要设置上一篇中提及在生产环境的环境变量。

# restart the python debug server
os.putenv('DJANGO_PRODUCTION_SETTINGS', 'TRUE')
try:
    subprocess.call("nohup python manage.py runserver 9999 &", shell=True)
except Exception:
    pass

OK,通过nohup启动之后我尴尬地发现在提交的时候远程卡住了除非我发出了终止指令。。。
这是因为通过ssh启动的nohup必须要重定向stdin,stdout,stderr。
stackoveflow传送门:Use SSH to start a background process on a remote server, and exit session
简单说一下linux的重定向,0是标准输入,1是标准输出,2是错误输出,如今我想把1和2都输入到一个文件中,不能直接使用1> file 2> file,因为这会引起文件的接受混乱,必须用特殊的语法,比如2>&1。

# work fine
subprocess.call("nohup python manage.py runserver 9999 > /nohup.out 2>&1 &", shell=True)

你可能感兴趣的:(python,git)