git在CICD实践中的应用10:Gitee仓库webhook使用(上)

一、背景

本系列前面的文章,主要集中在国外的服务器,本文尝试在国内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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
    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等第三方服务。

发布了481 篇原创文章 · 获赞 244 · 访问量 110万+

猜你喜欢

转载自blog.csdn.net/subfate/article/details/103093539