背景

在迁移飞书的过程中,原先的jenkins钉钉机器人功能也就需要迁移,别看他们工作方式很像,但是API不同,无法复用原先的钉钉通知插件。
不一样体现在哪里呢,举一个最简单的文字消息的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 以下是两个平台的post请求的json body内容
# 钉钉
{
"msgtype": "text",
"text": {
"content": "example msg"
}
}
# 飞书,确实,不能显得我在抄是吧
{
"msg_type": "text",
"content": {
"text": "example msg"
}
}

解决方式

  1. 找下现成的轮子
    1. 搜了一下字节有自己的一个方案,研究下来要配合Lark的一个进行使用,那个在内测阶段,也找不到相应的群聊机器人
  2. 搜一下大家的解决方案
    1. 写一个python脚本然后加一个shell执行的
    2. 不行的话curl一下也行
  3. 不如自己写一个吧

Jenkins

Jenkins 是由 Java 语言开发的最流行的 CI/CD 引擎。

如果自建过jenkins的话,应该都知道插件的丰富和重要性,不少方便的功能都是插件实现的,能想到的基本上都有插件实现。

首先你可能想自己搭一个jenkins

docker-compose的方式启动为例,不是本文重点,没有搭好的jenkin也可以开发

  • 可以参考这里https://www.jenkins.io/download/
  • 简单的docker-compose.yml实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    version: '3.7'
    services:
    jenkins:
    image: jenkins/jenkins:lts-jdk11
    privileged: true
    user: root
    restart: always
    environment:
    - JAVA_OPTS=-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8
    - JAVA_TOOL_OPTIONS=-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8
    - LC_ALL=C.UTF-8
    ports:
    - 8083:8080
    - 50003:50000
    container_name: my-jenkins-3
    volumes:
    - ~/jenkins_data:/var/jenkins_home
    - /var/run/docker.sock:/var/run/docker.sock
  • docker-compose up -d
  • 更新方式
    • 备份一下卷
    • 拉一下最新的docker镜像重启即可

      设置一些登录信息以及基础插件的安装

插件开发

准备工作

  • 安装JAVA JDK
  • 安装Maven
  • 输入mvn -v确认准备工作OK

创建Helloworld插件来建立基础理解

运行

1
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Choose archetype:
1: remote -> io.jenkins.archetypes:empty-plugin (Skeleton of a Jenkins plugin with a POM and an empty source tree.)
2: remote -> io.jenkins.archetypes:global-configuration-plugin (Skeleton of a Jenkins plugin with a POM and an example piece of global configuration.)
3: remote -> io.jenkins.archetypes:global-shared-library (Uses the Jenkins Pipeline Unit mock library to test the usage of a Global Shared Library)
4: remote -> io.jenkins.archetypes:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.)
5: remote -> io.jenkins.archetypes:scripted-pipeline (Uses the Jenkins Pipeline Unit mock library to test the logic inside a Pipeline script.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): :

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 4
Choose io.jenkins.archetypes:hello-world-plugin version:
1: 1.1
2: 1.2
3: 1.3
4: 1.4
5: 1.5
6: 1.6
7: 1.7
8: 1.8
9: 1.9
10: 1.10
11: 1.11
12: 1.12
13: 1.13
Choose a number: 13: 13
[INFO] Using property: groupId = unused
[INFO] Using property: package = io.jenkins.plugins.sample
[INFO] Using property: hostOnJenkinsGitHub = true

[INFO] Using property: groupId = unused
[INFO] Using property: package = io.jenkins.plugins.sample
[INFO] Using property: hostOnJenkinsGitHub = true
Define value for property 'artifactId': hello
Define value for property 'version' 1.0-SNAPSHOT: :
Confirm properties configuration:
groupId: unused
package: io.jenkins.plugins.sample
hostOnJenkinsGitHub: true
artifactId: hello
version: 1.0-SNAPSHOT
Y: :

按照提示依次选择hello-world-plugin, 最新版本, 输入插件id 回车到底。就创建好了hello目录。代码传到了这里

插件目录结构:

  • pom.xml: maven使用这个文件来构建插件。这里声明了插件的版本以及依赖关系
  • src/main/java:java源码。包括插件注册、jelly视图背后的数据绑定、业务逻辑
  • src/main/resources:jelly视图文件,用于在web界面上显示
  • src/main/webapp:静态的资源文件,例如图片和html文件,还能写js,暂时可以忽略
  • src/test: 测试文件

我们结合实际效果,重点看下java源码和jelly视图文件。


运行调试插件

执行mvn clean && mvn hpi:run

看到Jenkins is fully up and running代表本地已经启动了一个jenkins并且安装上了插件,可以访问http://127.0.0.1:8080/jenkins,进行效果查看![](./jenkin-plugin/local-jenkins.jpg)

helloword插件介入的是job的build流程,因此我们创建一个freestyle的job。在下图这个界面的Build栏目点击说,你好,世界

这里的对话框进行填写后,点击save按钮进行保存。

至此,我们创建了一个Job,并且在这个job的构建(Build)部分,添加了一个插件功能。
我们跑一下这个Job,会发现输出多了我们设置的欢迎词


我们可以把插件分为两个功能部分

  • 参数的编辑和存储,官方文档

    • 视图: src/main/resources/io/jenkins/plugins/sample/HelloWorldBuilder目录。
      • 其中config.jelly是核心,定义了html表格以及用到的数据字段
      • help-x.html和Messages.properties对应的是问号的内容和表格输入的校验文案
    • 配合视图使用的java类src/main/java/io/jenkins/plugins/sample/HelloWorldBuilder.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
        // 视图关联部分代码节选
    public class HelloWorldBuilder extends Builder implements SimpleBuildStep {

    private final String name;
    private boolean useFrench;

    @DataBoundConstructor // 将job存储的值读进来
    public HelloWorldBuilder(String name) {
    this.name = name;
    }

    // jelly渲染获取到值的关键,对应field中的name字段
    public String getName() {
    return name;
    }

    // 布尔值jelly渲染获取到值的关键,对应field中的useFrench字段
    public boolean isUseFrench() {
    return useFrench;
    }

    @DataBoundSetter // 将job存储的值读进来
    public void setUseFrench(boolean useFrench) {
    this.useFrench = useFrench;
    }

    @Symbol("greet")
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
    ...
    }
    }
    * 实际build过程中的业务
    * helloword插件实现的是在构建的output中打印一段字符串,我们要做的是覆写`SimpleBuildStep`中的perform函数即可
    ```java
    @Override
    public void perform(Run<?, ?> run, FilePath workspace, EnvVars env, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
    if (useFrench) {
    listener.getLogger().println("Bonjour, " + name + "!");
    } else {
    listener.getLogger().println("Hello, " + name + "!");
    }
    }
    • 其中,listener是本次构建的运行监听器,调用它的logger可以输出内容到output

其他扩展点

  • JobProperty
  • Build Triggers
  • Build
  • Post-build
  • RunListener

结合实际需求,参考类似扩展点的插件源码,实现自己的需求

回到我们原始的需求

参考现有的钉钉机器人,配置上主要扩展了两个点(全局配置,和JobProperty的配置),业务上扩展了RunListener。
钉钉机器人迭代的功能已经比较复杂,我们从简来实现第一版。即,仅支持配置webhooks,在JobProperty输入飞书机器人的webhooks地址列表,以";"分隔。发送时间则默认开始和结束均发送。
仓库地址

我们仅需要配置一个字符串即可,因此jelly模板src/main/resources/io/jenkins/plugins/FeishuJobProperty/config.jelly、以及src/main/java/io/jenkins/plugins/FeishuJobProperty.java数据配置java都很简洁

业务实现部分,我们实现RunListener类并且复写掉onStarted以及onCompleted函数,在这两个函数里发送一个HTTP的POST请求到飞书webhooks地址即可

  • 发送到飞书需要的信息用过onStarted传入的run都可以拿到(参考dingding机器人)
  • 参照飞书的card规范美化消息,消息构建和发送封了一个FeishuService。pom.xml引入了okhttp以及fastjson,可能需要处理依赖冲突,最后把冲突的依赖都指定了版本

Job的配置方式
从飞书复制机器人地址

在job的配置中填写地址并保存

构建一次,飞书收到消息的实际效果

参考资料

插件的发布

打包自己用

  1. 打包mvn clean && mvn package
  2. 在target目录下找到生成的hpi文件
  3. 到jenkins的全局系统管理 -> 插件管理 -> 上传插件选择hpi文件 -> 自动重启jenkins即可

发布到官方仓库

  • 仓库上传到自己的github
  • 这里新建issue
  • 后续的步骤持续更新中,目前github issue相关人员的回复是很快的,表示会深入看一下