一、背景
本系列前面的文章,主要集中在国外的服务器,本文尝试在国内git托管平台Gitee上进行实验。
Gitee官网为:https://gitee.com。因其在国内,对于速度要求较高的团队,可以考虑该平台。
Gitee拥有很多第三方集成,如Jenkins、阿里云、华为、Azure等,同时也提供了WebHook(Web钩子)。WebHook可以理解为一个处理post请求的机制,甚至简单理解为一个http地址。当提交代码后,Gitee会自动回调这个地址,这个地址需要我们编写程序进响应,以便处理(如编译代码、发邮件,自动部署等)。WebHook支持很多种触发事件,如Push、Tag Push、Issue等,可根据需要选择。
本文抛却第三方集成,使用自己写程序的来测试WebHook。以一个简单的实用功能为例:当提交代码到Gitee仓库后,自动发送通知邮件。
本文需要具备云主机服务器,以便提供公网的响应地址。如无,考虑第三方集成服务。
二、知识点
WebHook数据格式说明在这里。
WebHook数据类型分头部(Request Headers)和数据体(Request Payload)。
头部说明如下:
Content-Type: application/json # 默认为 application/json , 若是旧版钩子(已不维护)为 application/x-www-form-urlencoded
User-Agent: git-oschina-hook # 固定为 git-oschina-hook,可用于标识为来自 gitee 的请求
X-Gitee-Token: webhook password # 用户新建 WebHook 时提供的密码
X-Gitee-Event: Merge Request Hook # 标识触发的钩子类型
不过在实测中没有找到方法读取。
不同的钩子类型,其数据体亦不同。具体参考官方示例,以Push类型为例进行简单说明:
- hook_name:钩子名称,如"push_hooks"。
- password:密码,在设置WebHook时指定,可通过密码判断请求合法性。
- commits为数组,如果多次commit,但只有一次push,则所有的commit在此数组中。第0个元素为最新提交的信息。有用的字段:
- id:提交的哈希值,可不理会。
- message:提交信息,需要记录。
- timestamp:提交时间,格式为"2018-02-05T23:46:46+08:00"。
- url:本次提交的具体地址。
- author:作者信息(结构体)。
- committer:提交者信息(结构体)。与author可能是相同的。
- total_commits_count commit的总次数
- repository:仓库信息
- owner:仓库所有者信息。
- git_http_url:仓库地址。
- project:似乎与repository相同。
三、webhook脚本文件
当仓库有Push时,Gitee会自动将上述信息post到指定的地址,我们获取消息体并解析出来需要的字段:
仓库名称、提交者、提交日志、提交时间。
将这些信息组装后,发送到指定邮箱地址中。完成本文提到的功能。
下面使用NodeJS来实现。需要依赖的库有koa(提供web服务)以及nodemailer(提供email功能)。
package.json文件内容:
{
"name": "foobar",
"version": "0.0.1",
"description": "hello world",
"main": "server.js",
"scripts": {
"test": "run.sh"
},
"author": "Late Lee",
"license": "MIT",
"dependencies": {
"koa": "^2.11.0",
"koa-bodyparser": "^4.2.1",
"koa-router": "^7.4.0",
"nodemailer": "^6.3.1"
}
}
实现文件:
/*
文件名:server.js
功能:
gitee仓库Webhook应用实例:提交代码时发送邮件。
*/
const koa_router = require("koa-router");
const Koa = require("koa");
const koa_bodyparser = require("koa-bodyparser");
const router = koa_router();
const nodemailer = require("nodemailer");
const g_port = 4000;
// 创建与邮件对应列表
var g_list = [
["latelee/webhook.git", "[email protected]"],
["latelee/autoci.git", "[email protected]"],
["", "[email protected]"] // 最后一个为默认邮箱
];
// 参数:发件人名称,收件人,主题,正文(支持html格式)
function sendMail(aliasName, tos, subject, msg)
{
var from = "[email protected]";
const smtpTransport = nodemailer.createTransport({
host: 'smtp.exmail.qq.com',
secureConnection: true, // use SSL
secure: true,
port: 465,
auth: {
user: from,
pass: '1qaz@WSX',
}
});
smtpTransport.sendMail({
from : aliasName + ' ' + '<' + from + '>',
to : tos,
subject : subject,
html : msg
}, function(err, res) {
if (err)
{
console.log('error: ', err);
}
});
}
function nl2br(str, isXhtml) {
var breakTag = (isXhtml || typeof isXhtml === 'undefined') ? '<br />' : '<br>';
var str = (str + '').replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2');
};
// 响应地址为foobar
router.post("/foobar", async (ctx) => {
var ret = 0;
var passwd = ctx.request.body.password;
console.log(`got reuqest. body:`);
console.log(ctx.request.body);
// 在设置WebHook时指定,此处判断,不合法直接返回
if (passwd != "helloworldpasswd")
{
ret = -1;
}
else
{
var commit = ctx.request.body.commits[0];
var respo = ctx.request.body.repository;
var url = respo.git_http_url;
var output = new Object();
output = '<h2>' + respo.name + '仓库代码有提交,请及时更新。</h2>地址为:<a href=\"' + url + '\"target=\"_blank\">' + url + '</a><br>';
output += '<h3>Committer:</h3>' + commit.committer.username + "<br>";
output += '<h3>Commit time:</h3>' + commit.timestamp + "<br>";
output += '<h3>Commit log:</h3>' + nl2br(commit.message) + "<br>";
console.log(output);
// 匹配仓库及对应的邮箱列表
for (var i = 0; i < g_list.length; i++)
{
//console.log(`item: ${item[0]}, ${item[1]}`);
var found = url.includes(g_list[i][0]);
if (found)
{
console.log(`found at ${i} of ${g_list.length}`);
break;
}
}
console.log(`will sendto: ${g_list[i][1]}`);
// 发邮件通知
// TODO:参数可配置
sendMail('CI自动邮件通知', g_list[i][1], respo.name + '仓库代码更新', output);
}
var res = new Object();
res['ret'] = ret;
res['timestamp'] = Date.now(); // 当前时间戳
ctx.body = res; // 返回的是json,以便gitee获取,否则可能认为失败
});
function main()
{
var app = new Koa();
app.use(koa_bodyparser({
enableTypes:["json","test","form"],
onerror:function (err,ctx){
console.log("api service body parse error",err);
ctx.throw(400,"body parse error");
},
}));
app.use(router.routes());
app.listen(g_port);
console.log('Running a koa server at localhost[v1.0]: ', g_port)
}
main();
代码说明:
- 使用koa监听4000端口,响应页面地址为foobar。
- 判断请求数据的passwd字段,不合法直接返回。
- 针对不同项目使用g_list存储仓库地址及对应的邮箱地址(实际中不同项目组,其成员亦不同)。简单起见,可直接使用统一的邮箱地址。
- 需要返回值,否则Gitee页面提供Not found,不过已经处理完结,不返回亦可。
- 仓库和邮件地址根据实际情况修改。
在服务器运行:
npm install
node server.js
即可启动服务。
四、配置
在Gitee上建立仓库后,进入WebHook配置界面,过程如图1所示:
图1
WebHook配置过程如图2所示:
图2
点击添加后,可以进行测试,如果没有运行服务,则连接测试失败。成功会显示返回值。
五、测试
当提交代码后(触发Push钩子),服务器响应信息如图3所示:
图3
片刻后,收到邮件,如图4所示:
图4
小结
根据Gitee文档描述,执行的超时时长为5秒。笔者仅做简单测试,在其范围之内。
发件人无法自由快捷选择。只能通过列表进行匹配。
本文的方法,在小团队中可使用。如果复杂大型项目,考虑Jenkins等第三方服务。