使用 Bash 编写 GitHub Web Hook
将您的 GitHub 仓库提升到新的功能水平。
自从微软收购 GitHub 以来,过去一年我一直将我的 Git 仓库托管在私有服务器上。虽然我很享受设置这一切带来的机会和挑战,并且最终产品也很好地满足了我的需求,但这样做并非没有牺牲。 GitHub 提供了一个简洁的界面来配置许多 Git 功能,否则这些功能将需要比简单点击按钮更多的时间和精力。我最喜欢的 GitHub 简化实现的功能之一是 Web Hook。当 GitHub 应用程序中发生特定事件时,就会执行 Web Hook。执行后,数据将通过 HTTP POST
发送到指定的 URL。
本文将介绍如何设置自定义 Web Hook,包括配置 Web 服务器、处理来自 GitHub 的 POST 数据以及使用 Bash 创建一些基本的 Web Hook。
准备 Apache为了本项目的目的,让我们使用 Apache Web 服务器来托管 Web Hook 脚本。 Apache 用于运行服务器端 shell 脚本的模块是 mod_cgi
,该模块在主要的 Linux 发行版上都可用。
启用该模块后,就该配置 Apache 中的目录权限和虚拟主机了。使用 /opt/hooks 目录来托管 Web Hook,并将此目录的所有权授予运行 Apache 的用户。要确定运行 Apache 实例的用户,请运行以下命令(前提是 Apache 当前正在运行)
ps -e -o %U%c| grep 'apache2\|httpd'
这些命令将返回一个两列输出,其中包含运行 Apache 的用户的名称和 Apache 二进制文件的名称(通常为 httpd
或 apache2
)。使用以下 chown
命令授予目录权限(其中 USER
是先前 ps
命令中显示的用户名称)
chown -R USER /opt/hooks
在此目录中,将创建两个子目录:html 和 cgi-bin。 html 文件夹将用作虚拟主机的 Web 根目录,cgi-bin 将包含虚拟主机的所有 shell 脚本。请注意,随着在 /opt/hooks 下创建新的子目录和文件,您可能需要重新运行上述 chown
以验证对文件和子目录的正确访问权限。
以下是 Apache 中虚拟主机的配置
<VirtualHost *:80>
ServerName SERVERNAME
ScriptAlias "/cgi-bin" "/opt/hooks/cgi-bin"
DocumentRoot /opt/hooks/html
</VirtualHost>
将 ServerName
指令的值从 SERVERNAME
更改为将通过 Web Hook 访问的主机的名称。此配置提供了托管文件和执行 shell 脚本的基本功能。 DocumentRoot
指令使用本地系统上的绝对路径指定虚拟主机的根目录。 ScriptAlias
指令接受两个参数:虚拟主机中的绝对路径和本地系统上的绝对路径。虚拟主机中的路径映射到本地系统路径。 mod_cgi
处理对 ScriptAlias
指令中指定的路径发出的所有请求。(注意:本文未涵盖包括 SSL 或日志记录在内的任何其他配置。)
您需要对 HTTP 协议和 Bash 脚本编写有基本的了解,才能理解 CGI 脚本的工作原理。当向 HTTP 服务器发出请求时,会生成响应并发送回客户端。 HTTP 请求包含指示服务器如何处理请求的标头。同样,HTTP 响应包含指示客户端如何处理响应的标头。使用任何现代浏览器上的开发者工具可以非常简单地查看和分析 HTTP 流量。以下是一个简单的 HTTP 请求和响应示例
请求
POST /cgi-bin/clone.cgi HTTP/1.1
Host: hooks.andydoestech.com
Content-length: 86
{"repository":{"name":webhook-test","url":https://github.com/
↪bng44270/webhook-test"}}
响应
HTTP/1.1 200 OK
Date: Tue, 11 Jun 2019 02:44:52 GMT
Content-Length: 18
Content-Type: text/json
{"success":"true"}
该请求正在向位于 http://hooks.andydoestech.com//cgi-bin/ 中的 clone.cgi 文件发出 POST
请求。响应包含响应代码、处理请求的日期/时间、内容主体长度(以字节为单位)以及内容主体本身。虽然有时可能会通过 HTTP 发送二进制数据,但本文中的示例仅处理明文传输。
鉴于 Bash 具有强大的文本处理能力和可用命令,它非常适合构建和操作 HTTP 事务中的文本。如果上述 HTTP 请求要由 Bash 脚本处理,则它可能如下所示
#!/bin/bash
JSONPOST="$(cat -)"
echo "Date: $(date)"
echo "Content-Length: 18"
echo "Content-Type: text/json"
echo ""
echo "{\"success\":\"true\"}"
虽然此脚本缺乏逻辑,但它很好地说明了 HTTP POST
数据是如何作为 JSONPOST
变量捕获的,以及 HTTP 响应标头和数据是如何通过标准脚本输出返回给客户端的。
虽然许多 GitHub 资源可以触发 Web Hook,但本文特别关注推送事件,该事件在将数据远程推送到代码仓库时触发。当发出 Web Hook 的 HTTP POST 请求时,JSON 对象将发布到 URL。此 JSON 对象包含许多与推送操作相关的信息,包括有关仓库和数据推送中包含的提交的信息。用于从 POST JSON 中解析单个值的命令是 jq
,该命令在主要的 Linux 发行版上都可用。该命令的语法要求以点表示法指定所需的属性。例如,考虑以下从 GitHub 返回的 JSON 对象片段
{
"repository": {
"name": "webhook-test",
"git_url": "git://github.com/bng44270/webhook-test.git",
"ssh_url": "git@github.com:bng44270/webhook-test.git",
"clone_url": "https://github.com/bng44270/webhook-test.git",
}
}
要使用 jq
返回名为 clone_url
的属性的值,您将使用以下语法
jq -r '.repository.clone_url' <<< 'JSON'
在用 JSON 对象的文本表示形式替换 JSON 后,此命令将返回 HTTP 仓库克隆 URL。使用命令替换,可以将 JSON 属性的值分配给 Bash 变量,以便在脚本中使用。
Hook #1:简单备份我想介绍的第一个 Hook 将在托管 Web Hook 脚本的 Apache 服务器上创建仓库的备份。本示例将使用上述 VirtualHost 配置。以下是仓库备份 Web Hook 脚本
1 #!/bin/bash
2
3 REPODIR="/opt/hooks/html/repos"
4
5 json_resp() {
6 echo '{"result":"'"$([[ $1 -eq 0 ]] && echo "success"
↪|| echo "failure")"'"}'
7 }
8
9 POSTJSON="$(cat -)"
10
11 REPOURL="$(jq -r ".repository.clone_url" <<< "$POSTJSON")"
12 REPONAME="$(jq -r ".repository.name" <<< "$POSTJSON")"
13
14 echo "Content-type: text/json"
15 echo ""
16
17 if [ -d $REPODIR/$REPONAME ]; then
18 pushd .
19 cd $REPODIR/$REPONAME
20 git pull
21 json_resp $?
22 popd
23 else
24 mkdir $REPODIR/$REPONAME
25 git clone $REPOURL $REPODIR/$REPONAME
26 json_resp $?
27 fi
脚本开头的 REPODIR
变量指示将包含所有仓库目录的目录。 json_resp
函数允许在脚本中多次重用生成 JSON 响应的代码。与上面的示例一样,HTTP POST
数据被捕获在 POSTJSON
变量中。在第 11 行和第 12 行中,使用 jq
从 POSTJSON
变量中提取 clone_url
和 name 属性。第 14 行开始创建 HTTP 响应标头。第 17-27 行的 if
块确定仓库是否已被克隆。如果已被克隆,则脚本移动到仓库文件夹,拉取仓库更改,然后返回到原始工作目录。如果该文件夹不存在,则创建该目录,并将仓库克隆到新目录。请注意使用在脚本开头设置的 $REPODIR
变量。无论仓库是克隆还是更新被拉取,都会调用 json_resp
函数来生成响应 JSON,其中将包含一个名为“success”的属性,其值为“true”或“false”,具体取决于相应 git
命令的结果。
备份仓库可能很有用。凭借命令行上可用的众多构建工具,创建 Web Hook 来交付仓库中代码的构建包是有意义的。这可以构建成一个强大的解决方案,满足持续集成/部署 (CI/CD) 的需求。以下是构建/部署 Web Hook 脚本
1 #!/bin/bash
2
3 WEBROOT="/opt/hooks/html/archive"
4 REPODIR="/opt/hooks/html/repos"
5 WEBURL="http://hooks.andydoestech.com/archive"
6
7 json_package() {
8 echo '{"result":"'$([[ $1 -eq 0 ]] && echo
↪"\"success\",\"url\":\"$1\"" ||
↪echo "\"package failure\"")"'}'
9 }
10
11 run_make() {
12 [[ -d $REPODIR/$REPONAME/build ]] && make -s -C
↪$REPODIR/$REPONAME clean
13 if [ $1 -eq 0 ]; then
14 make -s -C $REPODIR/$REPONAME
15 if [ -d $REPODIR/$REPONAME/build ]; then
16 FILENAME="$REPONAME-$COMMITTIME.tar.gz"
17 tar -czf $WEBROOT/$FILENAME -C
↪$REPODIR/$REPONAME/build .
18 json_package "$?" "$WEBURL/$FILENAME"
19 else
20 echo '{"result":"build failure"}'
21 fi
22 else
23 echo '{"result":"clone/pull failure"}'
24 fi
25 }
26
27 POSTJSON="$(cat -)"
28
29 REPOURL="$(jq -r ".repository.url" <<< "$POSTJSON")"
30 REPONAME="$(jq -r ".repository.name" <<< "$POSTJSON")"
31 COMMITTIME="$(jq -r '.commits[0].timestamp' <<<
↪"$POSTJSON" | date -d "$(cat -)" +"%m-%d-%YT%H-%M-%S")"
32
33 echo "Content-type: text/json"
34 echo ""
35
36 if [ -d $REPODIR/$REPONAME ]; then
37 pushd .
38 cd $REPODIR/$REPONAME
39 git pull
40 run_make $?
41 popd
42 else
43 mkdir $REPODIR/$REPONAME
44 git clone $REPOURL $REPODIR/$REPONAME
45 run_make $?
46 fi
与 Hook #1 类似,变量在脚本开头定义,以指定将克隆仓库的目录、将存储构建包的目录以及构建包的基本 URL。在第 7-25 行定义的两个函数将在脚本后面使用。第 27-31 行正在捕获 JSON POST 数据,并使用 jq
将属性解析为 shell 变量。请注意,COMMITTIME
中的日期格式正在从其原始形式修改(稍后这将变得有意义)。第 33-46 行在设置 HTTP 标头和克隆/拉取仓库方面与 Hook #1 几乎相同,但添加了对 run_make
函数的调用。克隆/拉取的返回状态传递给 run_make
函数。如果克隆/拉取成功运行,则该函数假定仓库的根目录中存在 Makefile。假定 Makefile 的行为方式如下
- 当执行
make
时,解决方案将构建到仓库中名为“build”的文件夹中。 - 当执行
make clean
时,“build”文件夹将被删除。
从第 12 行开始,如果构建文件夹存在,则执行 make clean
以将其删除。如果第 13 行的 make 成功,则使用 REPONAME
和 COMMITTIME
构造存档文件名。请注意,COMMITTIME
的值不包含空格,以便获得正确的文件名。第 17 行的 tar
命令的状态代码将传递到 json_package
函数。如果成功创建了存档,则定义一个包含两个 JSON 属性的 JSON 对象:result
设置为“success”,url
设置为存档的 URL。如果无法创建存档,则 result 属性设置为“package failure”。
GitHub 提供了许多功能,但毫无疑问,Web Hook 为 DevOps 工程师提供了完成几乎任何任务的工具。以这种方式利用 Apache 与 CGI 和 Bash 脚本编写的功能,使其可以被 GitHub 使用,从而实现了几乎无限的可能性。
资源有关本文中提及的主题的更多信息,请参阅以下链接