使用DingTalk实现共享库自定义通知器
参考:https://www.ssgeek.com/post/jenkinssharelibrary-shi-jian-zhi-zi-ding-yi-tong-zhi-qi
和原博客不同的点:原博客用的是Http Request插件,本文用的是DingTalk插件
与任何编程环境一样,在Jenkins流水线中,集中化功能,共享公共代码和代码重用都是快速、有效地进行开发的基本技术,这些实践鼓励使用标准方法来调用功能,为更复杂的操作创建构建块并隐藏复杂性。他们还可以用于提供一致性以及鼓励约定优于配置以简化任务。
Jenkins允许用户完成所有这些操作的一个关键方法就是使用流水线共享库(pipeline share library)。共享流水线库是由存储在代码仓库中的代码组成的,该代码仓库由Jenkins自动下载并可供流水线使用。
以上中文描述来自《Jenkins 2权威指南》。
关于jenkins pipeline share library的更多介绍,可以参考官方文档
1、需求引入
随着devops
理念在公司越来越多的实践,jenkins
等工具的应用场景越来越多,当我们在执行完成某个流水线任务后,常常需要关注的是这个任务为什么执行,执行成功与否等等。于是就需要在执行完流水线后进行一定程度的消息推送,在现今的工作流中消息推送无外乎分为两大类:邮件和企业沟通协作软件,相比之下,我们可能更多的会去关注和使用沟通软件来发送消息而不是通过邮件的方式。而常用的企业沟通协作软件有以下几类:腾讯系的企业微信、阿里系的钉钉、字节跳动的飞书等等,当然有能力的企业也会自己研发这类软件。
本文示例以钉钉为例,通过流水线共享库实现自定义消息通知器。
2、钉钉机器人
钉钉的群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub
,GitLab
等源码管理服务,实现源码更新同步;通过聚合Trello
,JIRA
等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook
协议的自定义接入,支持更多可能性。
自定义钉钉机器人支持以下类型消息类型数据格式的推送,更多定义方法可参考官方的接口文档:
- text类型
- markdown类型
- 整体跳转ActionCard类型
- 独立跳转ActionCard类型
- FeedCard类型
钉钉机器人在2019
年的下半年进行过升级,在新增机器人时,需要选择一种安全条件(自定义关键词、加签、ip
地址或ip
地址段)来保障自定义机器人的安全。可以理解为即使机器人的token
泄漏,如果不知道设置的安全条件是什么,还是无法盗用的。
3、jenkins消息推送插件
这里要提到的是在jenkins
插件列表中有一个钉钉插件。
简单对此插件做了下分析:截止目前此插件在2020
年1
月份有相应代码提交,并且发布了2.0
版本,从jenkins
的插件官网中可以看到此版本的插件在在消息中支持了更多内容,效果如下,但是此插件目前还暂不支持流水线中使用
在此之前的上一版本提交记录已经是2018
年了,此插件使用方法类似,推送的消息效果如下
此版本支持在流水线中使用,相应内容如下
dingTalk accessToken: "xxx",
imageUrl: "xxx",
jenkinsUrl: "https://127.0.0.1:8080",
message: "项目构建成功",
notifyPeople: "155xxxx5533"
如上所示,在流水线脚本中配置钉钉机器人token
、图片路径、jenkins
地址、消息内容、要提醒的人手机号码即可,可以发现,此消息还是有局限性,不够友好。
因此在没有编写插件能力的情况下,我们可以通过更为灵活的自定义流水线共享库的形式,并且按照钉钉机器人的官方接口文档,自定义一个消息推送通知器。
4、自定义通知器的实现
4.1、内容定义
无论jenkins
任务的构建触发原因是使用者手动构建或通过代码推送的自动触发,往往关注此消息的人群是开发者们。因此通过一段时间的需求调研以及综合各方的建议,最终将消息推送的内容中包含了以下信息:
- 应用名称
- 构建结果
- 当前版本
- 构建发起
- 持续时间
- 构建日志
- 更新记录(包含用户提交的短日志,用户名称,提交时间)
每次构建结果通知中包含了以上就基本完备。
4.2、共享库创建
本文不过多介绍共享库具体的创建与在pipeline流水线中的引用方法,整体来说,共享库的代码目录结构如下
(root)
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
+- resources # resource files (external libraries only)
| +- org
| +- foo
| +- bar.json # static helper data for org.foo.Bar
官方描述:
src
目录应该看起来像标准的Java
源目录结构。当执行流水线时,该目录被添加到类路径下。
vars
目录定义可从流水线访问的全局变量的脚本。 每个 *.groovy
文件的基名应该是一个Groovy (~ Java)
标识符, 通常是camelCased
。 匹配*.txt
, 如果存在, 可以包含文档, 通过系统的配置标记格式化从处理 (所以可能是HTML
, Markdown
等,虽然txt
扩展是必需的)。
这些目录中的Groovy
源文件 在脚本化流水线中的CPS transformation
一样。
resources
目录允许从外部库中使用 libraryResource
步骤来加载有关的非Groovy
文件。 目前,内部库不支持该特性。
根目录下的其他目录被保留下来以便于将来的增强。
4.3、方法的具体实现
在var这个目录下创建一个名为dingding.groovy
的文件作为钉钉消息推送方法的代码文件。
构建一个消息通知器的主要思路:
- 消息指标内容从哪来
- 消息模板如何定义
- 消息怎么发送,发到哪里
4.3.1、消息来源
首先,消息内容从哪来,上面提到的需要在消息中体现的每个指标的可取的获取方式
指标名称 | 指标来源定义 |
---|---|
应用名称 | 定义为jenkins的任务名称,通过全局变量env.JOB_NAME获取或者在pipeline中自定义一个变量给出 |
构建结果 | 在pipeline中post字段指标判断并给出 |
当前版本 | 定义为jenkins的构建编号,通过全局变量env.BUILD_NUMBER或者在pipeline中自定义版本号 |
构建发起 | 通过全局变量env.BUILD_USER获取 |
持续时间 | 通过全局变量currentBuild.durationString获取,这个值更为友好 |
构建日志 | 日志太多,给个链接即可,通过全局变量env.BUILD_URL/console获取 |
更新记录 | 这个指标是指代码提交到版本库中的更新信息,而且包含提交时间,提交者名称,获取思路可以通过在检出代码后通过类似git log的命令过滤出或者根据全局变量currentBuild.changeSet获取 |
分析:
本文中的共享库用于jenkins+k8s
自动化ci
测试环境,因此某些指标的定义方法为:
应用名称自定义,用变量给出,在pipeline前文定义全局变量,在这里传入变量即可
当前版本自定义,以代码分支+commitid
作为docker
镜像的tag
,在pipeline前文中实现或亦通过共享库实现,在这里传入变量即可
更新记录根据全局变量获取,在这里通过代码实现
较为复杂的是如何解读currentBuild.changeSet
这个全局变量,通过jenkins上的全局变量列表文档查看如下
点击其中的链接查看官方文档
通过进一步查看官方文档得知,currentBuild.changeSet
返回的是一个集合,这个集合中包含了提交日志,commitid
,作者id
,作者全称,时间戳等信息,具体对象相关属性如下
currentBuild.changeSets{
items[{
msg //提交注释
commitId //提交hash值
author{ //提交用户相关信息
id
fullName
}
timestamp
affectedFiles[{ //受影响的文件列表
editType{
name
}
path: "path"
}]
affectedPaths[// 受影响的目录,是个Collection<String>
"path-a","path-b"
]
}]
}
因此,可以通过循环遍历得出我们需要的相关属性值,通过groovy
脚本定义方法并返回相应字符串,其中为了更优化,需要对提交日志做一下长度限制,对时间戳进行格式化,这两个功能需要不断调试。其中changeString
变量的赋值格式定义为markdown
的无序列表,最终方法如下
def getChangeString() {
def changeString = ""
def MAX_MSG_LEN = 20
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncatedMsg = entry.msg.take(MAX_MSG_LEN)
commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
changeString += " - ${truncatedMsg} [${entry.author} ${commitTime}]\n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
return (changeString)
}
4.3.2、消息模板定义
消息中的相关字段都获取到了,下一步需要做的就是定义一个消息模板,如果使用邮件发送通知,同样的也需要定义一个模板。
这里使用更为友好的markdown
格式来发送通知,钉钉机器人接口接收的消息是json
格式,具体内容可以通过查看官方文档,为了避免换行出错,手动指定换行符,最终的格式模板如下
{
dingtalk (
robot: 'b446da5d-6c82-41ba-8xxxxxxxxX',
type: 'MARKDOWN',
title: "${MYENV}_${PRONAME}构建通知",
text: [
"# $headMessage",
"# 构建详情",
"- 应用名称: ${PRONAME}",
"- 分支: ${MYENV}",
"- 构建结果:${statusMessage}",
"- 构建人: **${env.BUILD_USER}**",
"- 持续时间: ${currentBuild.durationString}",
"- 构建日志: [日志](${env.BUILD_URL}console)",
"# Jenkins链接",
"[应用Jenkins地址](http://xxxxxxxxx/job/${NAMESPACE}-${MYENV}_${PRONAME}/)"
],
at: ["13520419047"]
)
}
4.3.3、消息发送方法
在流水线中按照消息模板渲染好的消息发送给钉钉的接口地址,可以实现的方法包括但不限于以下几种:
- 通过执行
shell
命令发送,例如curl
命令指定参数即可,最为简单,但不够友好 - 通过
pipeline
语法和插件实现,例如使用[DingTalk插件],在Jenkins pipeline
中发送HTTP
请求给钉钉接口。 - 通过调用其他脚本发送,例如
python
脚本,较复杂,不推荐。
综上比较,选择一种友好且不复杂的方案,即通过pipeline
语法和插件实现
首先在插件安装中安装好DingTalk
插件
4.3.4、最终方法
综上所述,在调用此共享库方法时传入分支环境变量MYENV
、项目名称变量PRONAME
、命令空间变量NAMESPACE
、构建状态变量statusMessage
、标题信息变量headMessage
,并结合前面实现的方法内容,最终方法dingding.groovy
内容如下
#!/usr/bin/env groovy
package com.XXXX
/* dingmes.groovy
##################################################
# Created by xxxxxxxxxxxx #
# #
# A Part of the Project jenkins-library #
##################################################
*/
def getChangeString() {
def changeString = ""
def MAX_MSG_LEN = 10
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncatedMsg = entry.msg.take(MAX_MSG_LEN)
commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
changeString += " - ${truncatedMsg} [${entry.author} ${commitTime}]\n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
return (changeString)
}
def notice(MYENV,PRONAME,NAMESPACE,statusMessage,headMessage){
wrap([$class: 'BuildUser']){
dingtalk (
robot: 'b446da5d-6c82-41ba-xxxxxxxxx',
type: 'MARKDOWN',
title: "${MYENV}_${PRONAME}构建通知",
text: [
"# $headMessage",
"# 构建详情",
"- 应用名称: ${PRONAME}",
"- 分支: ${MYENV}",
"- 构建结果:${statusMessage}",
"- 构建人: **${env.BUILD_USER}**",
"- 持续时间: ${currentBuild.durationString}",
"- 构建日志: [日志](${env.BUILD_URL}console)",
"# Jenkins链接",
"[应用Jenkins地址](http://xxxxx/job/${NAMESPACE}-${MYENV}_${PRONAME}/)"
],
at: ["13520419047"]
)
}
}
4.4、方法调用
此消息通知的方法通常在pipeline
的post
部分调用,如下所示
post{
success{
script{
dingding.notice("$MYENV","$PRONAME","$NAMESPACE","构建成功✅","✅✅✅✅✅✅✅✅✅")
}
}
failure{
script{
dingding.notice("$MYENV","$PRONAME","$NAMESPACE","构建失败❌","❌❌❌❌❌❌❌❌❌")
}
}
}
4.5、最终效果
测试代码提交,执行流水线,最终的消息通知效果如下图
✅✅✅✅✅✅✅✅✅
构建详情
应用名称: gateway
分支: test
构建结果:构建成功✅
构建人: xxxx
持续时间: 58 sec and counting
构建日志: 日志
Jenkins链接
应用Jenkins地址
@xxxx
5、总结
至此,本文记录通过自定义jenkins pipeline
流水线共享库方法,实现了较为灵活的自定义钉钉机器人消息通知。如果是使用企信等其他软件,与此实现思路相近。