设为首页 收藏本站
查看: 790|回复: 0

[经验分享] git post-receive 待验证的代码

[复制链接]

尚未签到

发表于 2018-1-13 15:45:45 | 显示全部楼层 |阅读模式

  • 本站文章除注明转载外,均为本站原创或者翻译。
  • 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处,尊重我的劳动,也尊重你的智商;
  • 本站部分原创和翻译文章提供markdown格式源码,欢迎使用文章源码进行转载;
  • 本博客采用 WPCMD 维护;
  • 本文标题:使用 git post-receive 钩子部署服务端代码
  • 本文链接:http://zengrong.net/post/2221.htm
  在 git 中提交服务器源码的时候,如果能够直接更新到测试服务器,并且重启服务使其生效,会节省懒惰的程序员们大量的时间。
  git 的 Server-side hook (服务端钩子/挂钩)可以用来做件事。
  本文以部署基于 OpenResty 的服务端程序为例来介绍我的做法。
  技术信息


  • OS: CentOS 6.3
  • 服务器软件: OpenResty
  • 开发语言: Lua
  名词解释


  • 服务器: 服务器硬件 + OS
  • 服务端程序: OpenResty 在服务器中的进程
  • 服务端代码: 部署在 OpenResty 中的 Lua 源程序
一、git 服务端钩子类型
  Pro git 中介绍了 git 钩子的几种类型,其中和服务端相关的有:


  • pre-receive  在客户端推送时最先执行,可以用它来拒绝客户端的推送。

  • update  与 pre-receive 类似,但会在每个分支都执行一次。

  • post-receive  在客户端推送完成后执行。
  根据我的需求,我选择 post-receive 钩子来做这件事。因为我不希望拒绝客户端的推送(那样程序员们可能不知道该怎么办)。推送总是会成功的,只是 命令 不成功而已。碰到 命令 不成功的情况,客户端只需要再次推送一个正确的 命令 即可。
  关于 命令 的配置,后面会详述。

二、git repostories
  我建立了2个 git 仓库来完成这个任务。分成2个仓库的好处是,更新服务端代码和控制服务端程序互不干扰。
  在开发服务器上,我可以将 OpenResty 的 lua file 缓存关闭。这样服务端代码更新后会立刻生效,不必重启服务端程序。
  而如果服务端程序出现错误,只需要更新它的状态(reopen/reload 等)即可,不必更新服务端代码。

server.git
  这个仓库保存服务端逻辑代码,客户端的文件夹结构如下:
  

server  
├── README.md
  
└── src
  └── main.lua
  

  

  每次提交代码的时候,在提交信息中可以包含特定的 命令 ,在推送这次提交时,git 服务端钩子就会起作用,将提交的代码更新到合适的地方。
  如果提交信息中没有特定的 命令 ,那么这就是一次普通的推送而已。
  在本例中,钩子所做的事情就是将 src/ 文件夹中的所有代码更新到服务端程序中。

serverctrl.git
  这个仓库是空的,永远不会有内容。通过在提交信息中包含特定的 命令,git 服务器钩子会对我们的服务端程序进行给定的操作。

三、使用钩子重启服务端程序

OpenResty 的进程控制
  使用 nginx 自己提供的 -s 参数来控制服务端程序:
  

nginx -s [stop|quit|reopen|reload] -p /path/to  

  

  不带 -s 参数,就是 start 功能了:
  

nginx -p /path/to  

  


命令
  serverctrl 是个空项目,不可能有任何内容。因此我规定提交信息中直接写操作命令即可。
  要控制服务端程序重启,只需要进行这样的提交和推送:
  

git commit --allow-empty -m 'reopen' && git push origin zrong  

  

  执行 [start|stop|quit|reload] 也是一样道理。

具体代码
  下面的代码展示了 serverctrl 项目中 post-receive 钩子的内容。钩子可以用操作系统能够识别的任意脚本语言来撰写。这里我使用的是 Python (2.7和3.4通用)。
  下面的代码和注释已经非常清楚了,我说一下几点要注意的。


  • 在 callnginx 方法中,需要把 subprocess.check_output 方法的 stderr 参数设置为 STDOUT 。因为如果执行 nginx 出错,出错信息默认是写入到 STDERR 中的,如果不进行这样的修改,出错时返回的输入信息就是空的。
  • 需要把 nginx 程序以及 ‘/opt/openresty/nginx’ 整个文件夹和其下文件的 owner 设置为 git 用户,否则钩子将没有权限启动 nginx 进程。
  • post-receive 钩子本身的 owner 也要设置成 git 用户,并给予执行权限。
  • 如果已经有一个 -p 参数(prefix)相同的 nginx 进程在运行了,注意先将其结束。否则 git 用户可能无权关闭这个进程。
  –
  

#!/usr/bin/env python  
import sys
  
import os
  
import subprocess
  

  
# prefix 配置路径
  
openresty = '/opt/openresty/nginx'
  
# 执行文件路径
  
nginx = openresty + '/sbin/nginx'
  
# 能够识别的信号
  
signals = ('start', 'stop', 'quit', 'reopen', 'reload')
  
# 支持的分支(用户)
  
branches = ('master', 'allen', 'zrong', 'xiefei', 'zm')
  

  
def getgitargs(*args):
  if args:
  return ['git', '--bare', '--git-dir', os.getcwd()] + list(args)
  return []
  

  
def callgit(*args):
  return subprocess.check_output(['env', '-i'] + getgitargs(*args),
  universal_newlines=True)
  

  
def callnginx(action):
  args = [nginx, '-p' ,openresty]
  if action != 'start':
  args += ['-s', action]
  return subprocess.check_output(args,
  stderr=subprocess.STDOUT, universal_newlines=True)
  

  
# 钩子会将信息从 STDIN 写入,将这些信息读入变量
  
oldrev,newrev,refname = sys.stdin.readline().strip().split(' ')
  
# 对我们的程序而言,只有 branch 名称有用
  
branch = refname.split('/')[-1]
  
print('oldref:%s, newrev:%s, refname:%s'%(oldrev, newrev, refname))
  
if not branch in branches:
  print('The branch "%s" is not in available list! '
  'The list is %s.'%(branch, str(branches)))
  exit(1)
  

  
try:
  # 得到当前提供的 branch 下的最新提交信息
  commitmsg = callgit('log', branch, '--oneline', '-1')[8:-1]
  print(branch+' '+commitmsg)
  if commitmsg in signals:
  callnginx(commitmsg)
  print('======= %s %s SUCCESS ======='%(branch, commitmsg))
  else:
  print('The signal "%s" is not in available list! '
  'The list is %s.'%(commitmsg, str(signals)))
  
except subprocess.CalledProcessError as err:
  print('======= %s %s %s ERROR ======='%(branch, commitmsg, err.output))
  exit(1)
  

  
exit(0)
  

  


四、从服务器的 git bare repostory 中部署服务端代码
  在这个例子中,我假设 git 仓库和测试服务器处于同一台服务器上。这也是小团队开发比较普遍的情况。

服务端代码的结构
  服务端代码位于 /opt/openresty/nginx/lua 这个文件夹中。在这个文件夹中有按照用户名称(分支名称)建立的子文件夹,使用这种方式,可以让多个开发者的服务端代码互不干扰,独立运行。
  下面的文件夹结构展示了目前有两个开发者 master 和 zrong 已经部署了自己的服务端代码。
  

/opt/openresty/nginx/lua/  
├── master
  
│   └── main.lua
  
└── zrong
  └── main.lua
  

  

  程序员甚至可以选择部署别人的代码到自己的文件夹中。它只需要在执行命令的时候提供希望部署的用户的分支名称(或者干脆提供一个 git 的 commit sha1),就能达到这种效果。

命令
  server 包含服务端代码,命令需要区分是代码内容推送还是代码部署推送,或者二者兼有。
  命令的设计如下:
  若提交信息的第一行包含 UP 字样,就代表是命令推送。如果同时还有其他的提交信息,可以换行写。
  如果只是普通的提交,那么直接写就可以了。
  假设当前的 git 库位于 zrong 分支,而且当前的 git 仓库是干净的,下面是几个例子:

Sample 1
  将已经提交过的代码推送到服务器,并更新服务端代码:
  

git commit --allow-empty -m 'UP' && git push origin zrong  

  


Sample 2
  将 server 仓库中的 allen 分支的内容更新到 /opt/openresty/nginx/lua/zrong :
  

git commit --allow-empty -m 'UP allen' && git push origin zrong  

  


Sample 3
  将 sha1 为 7ebbf4f3151e2dfd5bdcbe9fe276fc9b6afd25e7 的提交中的内容更新到 /opt/openresty/nginx/lua/zrong :
  

git commit --allow-empty -m 'UP 7ebbf4f' && git push origin zrong  

  


导出一个 bare 仓库中的内容
  位于服务器上的 git 仓库,通常是 bare 的,它没有 work-tree ,不能直接通过复制的方式来更新服务端代码。
  但我们可以使用 git archive 命令先将仓库中的代码导出成一个包,然后再解压这个包到合适的部署路径即可实现服务端代码更新。
  下面的代码做这样几件事:


  • 找到 server.git 这个 bare 仓库;
  • 处理 zrong 分支;
  • 将 src/ 文件夹打包成 src.tar 文件。
  –
  

git --git-dir server.git archive -o src.tar zrong src/  

  

  如果只想要其中的一个文件,我们可以用 git show 把这个文件的内容输出到一个文件:
  

git --git-dir server.git show zrong:src/main.lua > main.lua  

  


具体代码
  下面的代码展示了 server 项目中 post-receive 钩子的内容。
  

#!/usr/bin/env python  
import sys
  
import re
  
import os
  
import subprocess
  
import shutil
  

  
luahome = '/opt/openresty/nginx/lua'
  

  
# 使用正则获取命令代码和要处理的分支信息
  
actionfmt = r'^UP ?(\w+)?$'
  
branches = ('master', 'allen', 'zrong', 'xiefei', 'zm')
  

  
def getgitargs(*args):
  if args:
  return ['git', '--bare', '--git-dir', os.getcwd()] + list(args)
  return []
  

  
def callgit(*args):
  return subprocess.check_output(['env', '-i'] + getgitargs(*args),
  universal_newlines=True, stderr=subprocess.STDOUT)
  

  
# 从提交信息中得到要处理的真正分支
  
def getref(branch):
  commitmsg = callgit('log', branch, '--oneline', '-1')[8:-1]
  matchobj = re.search(actionfmt, commitmsg)
  if matchobj:
  if matchobj.group(1):
  return matchobj.group(1)
  return branch
  return None
  

  
# 将 server/src 备份到一个文件
  
def archive(refname):
  tarfile = '%s/%s.tar'%(luahome, refname)
  callgit('archive', '-o', tarfile, refname, 'src/')
  return tarfile
  

  
# 解压缩备份文件到正确的文件夹
  
def tarxf(tarfile, refname, branch):
  refdir = os.path.join(luahome, branch)
  if os.path.exists(refdir):
  shutil.rmtree(refdir)
  args = ['tar', 'xf', tarfile, '-C', luahome]
  output = subprocess.check_output(args,
  stderr=subprocess.STDOUT, universal_newlines=True)
  shutil.move(os.path.join(luahome, 'src/'), refdir)
  os.remove(tarfile)
  return output
  

  
oldrev,newrev,refname = sys.stdin.readline().strip().split(' ')
  
print('oldref:%s, newrev:%s, refname:%s'%(oldrev, newrev, refname))
  

  
branch = refname.split('/')[-1]
  
if not branch in branches:
  print('The branch "%s" is not in available list! '
  'The list is %s.'%(branch, str(branches)))
  exit(1)
  

  
try:
  refname = getref(branch)
  if refname:
  tarfile = archive(refname)
  succ = tarxf(tarfile, refname, branch)
  print('update [%s] by [%s] success.'%(branch, refname) )
  print('======= update [%s] by [%s] SUCCESS ======='%(branch, refname))
  
except subprocess.CalledProcessError as err:
  print('update [%s] by [%s] error: %s'%(branch, refname, err.output))
  print('======= update [%s] by [%s] ERROR ======='%(branch, refname))
  exit(1)
  

  
exit(0)
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-434721-1-1.html 上篇帖子: 使用Gitosis搭建Git服务器 下篇帖子: Git总结笔记4-git push origin master 报错的解决方法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表