引入钉钉机器人通知 Gitlab CI/CD 的构建状态

1,256 阅读4分钟

本文是2020年的文章,在公司当时的环境写的了,现在大部分公司都有Devops效能平台采用docker或者oss部署

前言

本篇是之前写的《利用 Gitlab CI/CD 实现自动构建,自动部署》的续篇。我们知道 Gitlab 的 Pipeline 中的 Job 执行成功或者失败之后,对应 Job 状态会改变为 passedorfailed,当 Job 的状态改变之后, 我们需要再到 Gitlab CI/CD 的页面下查看 Job 的状态,去看看有没有打包完成。
实践表明,某些时候我们的 gitlab 执行 Job 往往需要等待很长且不稳定。导致我们反复到 CI/CD 的页面看看 Job 是否执行完毕。所以我们需要一个更友好,高效的 Job 执行完成状态的反馈方案。由此我想到了钉钉通知机器人。

引入钉钉机器人

其实就是在一个钉钉群引入 webhook 机器人,然后给你提供 token 和 secret, 依照指定的格式发起 http 请求就可以了

详情请看:ding-doc.dingtalk.com/doc#/server…

官方文档解析很清楚,需要注意点就是需要加签这个步骤:

/* 把timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,\n
最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)。
- 这里用node的crypto加密模块实现,大概实现如下
*/
const encryptSign = (secret, content) => {
  const str = crypto
    .createHmac("sha256", secret)
    .update(content)
    .digest()
    .toString("base64");
  return encodeURIComponent(str);
};
const sign = encryptSign(this.secret, timestamp + "\n" + this.secret);

定制钉钉机器人

钉钉机器人支持多种类型消息,我们需要的是一个文本和卡片类型消息,图片如下。
image.png
结合钉钉文档,我们封装了自己的机器人 dingtalkBot 类,支持快速发送文本和卡片消息。脚本如下:

dingtalkBot.js

/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto");
const axios = require("axios");
const encryptSign = (secret, content) => {
  const str = crypto
    .createHmac("sha256", secret)
    .update(content)
    .digest()
    .toString("base64");
  return encodeURIComponent(str);
};

/**
 * 钉钉机器人 WebHook:用于支持钉钉机器人消息发送
 *
 * 官方文档:https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq
 */
class DingtalkBot {
  /**
   * 机器人工厂,所有的消息推送项目都会调用 this.webhook 接口进行发送
   *
   * @param {String} options.webhook 完整的接口地址
   * @param {String} options.baseUrl 接口地址
   * @param {String} options.accessToken accessToken
   * @param {String} options.secret secret
   */
  constructor(options) {
    options = options || {};
    if (!options.webhook && !(options.accessToken && options.baseUrl)) {
      throw new Error("Lack for arguments!");
    }
    // 优先使用 options.webhook
    // 次之将由 options.baseUrl 和 options.accessToken 组合成一个 webhook 地址
    this.webhook =
      options.webhook ||
      `${options.baseUrl}?access_token=${options.accessToken}`;
    this.secret = options.secret;
  }

  /**
   * 发送钉钉消息
   *
   * @param {Object} content 发动的消息对象
   * @return {Promise}
   */
  send(content) {
    let singStr = "";
    if (this.secret) {
      const timestamp = Date.now();
      singStr =
        "&timestamp=" +
        timestamp +
        "&sign=" +
        encryptSign(this.secret, timestamp + "\n" + this.secret);
    }
    return axios.request(this.webhook + singStr, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      data: JSON.stringify(content),
    });
  }

  /**
   * 发送纯文本消息,支持@群内成员
   *
   * @param {String} content 消息内容
   * @param {Object} at 群内@成员的手机号
   * @return {Promise}
   */
  text(content, at) {
    at = at || {};
    return this.send({
      msgtype: "text",
      text: {
        content,
      },
      at,
    });
  }

  /**
   * 发送actionCard(动作卡片)
   * Ps: 支持多个按钮,支持Markdown
   *
   * @param {String} card.title 标题
   * @param {String} card.text 消息内容
   * @param {String} card.btnOrientation 按钮排列的方向(0竖直,1横向,默认为0)
   * @param {String} card.btn.title 某个按钮标题
   * @param {String} card.btn.actionURL 某个按钮链接
   * @return {Promise}
   */
  actionCard(card) {
    return this.send({
      msgtype: "actionCard",
      actionCard: {
        title: card.title,
        text: card.text,
        btnOrientation: card.btnOrientation || 0,
        btns: card.btns || [],
      },
    });
  }
}

module.exports = DingtalkBot;

定制消息模板

上面的消息类型我们知道,我们需要一些构建信息(项目,分支,环境等),还有需要@指定的提交人。经过折腾,我们知道 gitlab runner 执行 node 脚本时, process.env 这个变量下包含着我们需要的所有变量,如图所示
image.png

上面的截图只是一部分


由此我们定义如下的 markdown 格式模板
image.png
除了卡片消息之外。我们需要@通知指定提交人。依据钉钉提供的 API, 我们需要提交人的电话号码信息。那我们暴露提交人的电话号码信息在 node 执行 js 的时候呢。同样经过折腾,我们可以为项目设置环境变量,gitlab runner 执行 Job 时,就会暴露在其中执行环境中,详情看:docs.gitlab.com/help/ci/var…
例如:
image.png
这里设置了 luxuemin 的变量名,变量值为我的号码:xxxxx。
注意:这里存在映射关系, 变量名为 luxuemin, 是我的 gitlab 平台上的用户名,应该也是 git 的用户名,如图image.png
配置好映射关系,我们就能@指定的提交人了。

const gitlabUserName = process.env.GITLAB_USER_NAME;
const phoneNumber = process.env[gitlabUserName]; // 在gitlab CI/CD上配置GITLAB_USER_NAME映射的电话号码

await robot.text("请查收你的构建信息", {
  atMobiles: [phoneNumber],
  isAtAll: false,
});

发送消息脚本文件notify.js

上面的脚本文件还加了一个 Github Today Trending 的小彩蛋(随机推荐一个 github 的今日流行趋势项目),和随机一张养眼图片。

如图:image.png

在之前的脚本中使用

根据模拟 jenkins 请求的结果,node 执行上面的脚本之前,携带成功,失败状态,这里用 status 表示, 同时推出 js 脚本是,用 process.exit(status)退出,可参考下面脚本

RES=$( curl -X POST \
       --user xxx:xxxx \
       --data 'json={"parameter":[{"name":"PJ","value":"crm-finance-static"},{"name":"MYENV", "value":"'$env'"},{"name":"TAG","value":"'$branch'"}]}' \
       --compressed \
       'http://xxxx/job/tao.tao/build?delay=0sec' \
     )
# if [ "$RES" ]; then { echo 'failed!'; exit 1;  } fi
if [ "$RES" ]; then
  node scripts/notify.js $env $branch --status=1 || { echo 'failed!'; exit 1;}
else
  node scripts/notify.js $env $branch --status=0 && { echo 'succused!'; exit 0;}
fi

原文地址:github.com/jackluson/l…