您的位置:首页 > 其它

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

2020-02-06 10:39 399 查看

一、背景

本系列前面的文章,主要集中在国外的服务器,本文尝试在国内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", "foo@bar.com"],
    ["latelee/autoci.git", "hello@163.com"],
    ["", "foo@foo.org"] // 最后一个为默认邮箱
    ];
    
    // 参数:发件人名称,收件人,主题,正文(支持html格式)
    function sendMail(aliasName, tos, subject, msg)
    {
    var from = "cicd@latelee.org";
    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等第三方服务。

    • 点赞
    • 收藏
    • 分享
    • 文章举报
    李迟 博客专家 发布了476 篇原创文章 · 获赞 240 · 访问量 109万+ 他的留言板 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: