介绍
Jenkins的很多功能都是借助它的插件机制来实现的,它本身提供的功能是很少的。开发Jenkins插件是基于扩展点(ExtensionPoint)来完成的,它是Jenkins系统的某个方面的接口或抽象类,这些接口定义了需要实现的方法,而Jenkins插件需要实现这些方法。更多扩展点的详细信息,可以参考官方ExtensionPoint文档,通过这些扩展点我们可以写插件来实现自己的需求。
下面是一些常用的扩展点:
- SCM:代表源码管理的一个步骤,如Git,CVS,SVN等就是扩展的SCM
- Builder:代表构建的一个步骤,我们可以增加一个构建步骤,而每个选项都是对应一个Builder,在每一个Builder中都有自己不同的功能。
- Publisher:代表构建后的一个步骤,比如通知,上传文件到目标服务器等,我们要开发的Telegram通知插件则是基于此扩展点实现。
- Trigger:代表一个构建的触发操作,当满足什么样的条件才可以触发这个项目的构建。比较常用的触发就是代码变更时自动触发。
环境搭建
Jenkins是基于Java语言开发的,因此它的插件也应该基于Java来进行开发。它需要安装Java开发环境和Maven,至于如何搭建Java和Maven开发环境,网上有很多教程,这里不做介绍。
安装完成后,修改maven用户目录下的settings.xml文件(如果用户目录没有settings.xml文件,则从maven安装目录下的conf目录复制到用户目录,maven安装目录下的settings.xml是全局配置,针对所有用户,不建议更改)
用户目录下settings.xml文件位置:
- linux:~/.m2/settings.xml
- windows:%USERPROFILE%.m2\settings.xml
修改为以下内容:
org.jenkins-ci.tools
jenkins
true
repo.jenkins-ci.org
https://repo.jenkins-ci.org/public/
repo.jenkins-ci.org
https://repo.jenkins-ci.org/public/
repo.jenkins-ci.org
https://repo.jenkins-ci.org/public/
m.g.o-public
创建插件
- 在需要创建插件的目录打开cmd窗口,运行一下命令
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:"
这个命令将允许你生成与Jenkins相关的多个项目原型之一,这里我们使用empty原型1.5版,因此选择如下:
$ mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:"
…
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): : 1
Choose io.jenkins.archetypes:hello-world-plugin version:
1: 1.0
2: 1.1
3: 1.2
4: 1.3
5: 1.4
6: 1.5
Choose a number: 6: 6
…
[INFO] Using property: groupId = unused
Define value for property 'artifactId': TelegramNotification
Define value for property 'version' 1.0-SNAPSHOT: : 1.0
[INFO] Using property: package = io.jenkins.plugins.sample
Confirm properties configuration:
groupId: unused
artifactId: TelegramNotification
version: 1.0
package: io.jenkins.plugins.sample
Y: : y
- 让我们确保可以构建插件,执行以下命令进行验证
mvn verify
验证结果如下,则说明验证通过。
(如果使用的是其他原型的话,在测试用例这一步可能会失败,则可以考虑在pom.xml中增加以下配置忽略测试用例)
org.apache.maven.plugins
maven-surefire-plugin
true
编写插件
- 创建一个TelegramPublisher实现Notifier类
public class TelegramPublisher extends Notifier {
}
在Jenkins的插件中,每一个插件类中都必须要有一个Descriptor内部静态类,它代表一个类的描述者,用于指明这个一个扩展点的实现,Jenkins是通过这个描述者才能知道我们自己写的插件。这个静态类需要被@Extension注解,Jenkins内部会扫描@Extension注解来知道注册了哪些插件。
public class TelegramPublisher extends Notifier {
@Symbol("Telegram")
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor {
@Override
public boolean isApplicable(Class extends AbstractProject> aClass) {
return false;
}
@Override
public String getDisplayName() {
return "Telegram Notification";
}
}
}
在Descriptor类中有两个方法一定需要我们进行重写
- isApplicable方法:这个方法的返回值代表这个Publisher在项目中是否可用,我们可以将我们的逻辑写在其中,例如判断一些参数,最后返回true或者false来决定在项目中是否可用。
- getDisplayName方法:这个方法返回的是一个String类型的值,这个名称会用在构建选项下拉框中显示的名称。
- 全局配置定义
插件如果需要在全局配置中进行配置的话,我们需要在Descriptor中定义一个属性。这里我们需要把Telegram Bot Token和Telegram Bot Name保存到全局配置中,则可以在这里进行定义。
- 定义botUserName和botToken(必须与global.jelly中的field相同,global.jelly是Jenkins中的全局配置视图,在后面进行详细介绍)
//全局配置中,进行Telegram Bot Name设置,必须与global.jelly中的field字段相同
private String botUserName;
//全局配置中,进行Telegram Bot Token设置,必须与global.jelly中的field字段相同
private String botToken;
public String getBotUserName() {
return botUserName;
}
public String getBotToken() {
return botToken;
}
- 在Descriptor构造函数中使用load()进行加载全局配置
public DescriptorImpl() {
load();
}
- 在插件中获取配置信息并持久化到xml中
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
botUserName = json.getString("botUserName"); //获取Telegram Bot Name配置
botToken = json.getString("botToken"); //获取Telegram Bot Token配置
save(); //将全局配置信息持久化到xml中
return super.configure(req, json);
}
- 在global.jelly中定义全局配置视图,这部分后面讲视图的时候再来详细讲解。
注意:这里的field字段必须与Descriptor里面的属性保持一致,才能绑定上。
使用mvn hpi:run
查看运行效果
在Manage Jenkins -> Configure System里面,我们已经看到了相关设置了。
- 项目配置定义
项目的相关配置,则是定义在插件类本身中,这里就是我们的TelegramPublisher类,其视图则是定义在config.jelly中,规则和global.jelly一样。
- 获取全局配置信息
我们定义一个方法来获取Descriptor类来获取全局配置信息,代码如下:
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
- 参数注入
// 项目配置,Telegram消息接收者
private final String receivers;
// 项目配置,额外消息
private final String messageTemplate;
// 项目配置,消息发送条件,构建成功是否发送消息
private final Boolean condition;
@DataBoundConstructor
public TelegramPublisher(String receivers, String messageTemplate, Boolean condition) {
this.receivers = receivers;
this.messageTemplate = messageTemplate;
this.condition = condition;
}
public String getMessageTemplate() {
return messageTemplate;
}
public String getReceivers() {
return receivers;
}
public Boolean getCondition() {
return condition;
}
这三个属性的值是项目配置过程中输入,由Jenkins从Web前端界面传递过来,通过在TelegramPublisher构造函数中进行参数的注入。这个类似于Spring的依赖注入,需要用@DataBoundConstructor注解标注。
- 在config.jelly中定义项目视图
运行效果图如下:
- 插件业务逻辑
在每个插件的perform()方法中,是真正开始执行的地方,我们如果要在插件中完成什么事,是在perform方法中实现,其中的方法参数build代表当前构建,launcher代表启动进程,listener代表一个监听器,可以将运行的内容信息通过listener输出到前台console。
@Override
public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println("[INFO] test message" + getDescriptor().getBotUserName());
return true;
}
触发一次构建输出日志如下:
Jenkins视图
Jenkins使用jelly来编写视图,Jelly是一种基于Java技术和XML的脚本编制和处理引擎。在Jenkins中的视图类型有三种
- global.jelly 全局配置视图
- config.jelly 项目配置视图
标签用来标记一组属性集合。
标签代表这是一个属性,其中title
是指界面上显示的字段名,而field
是属性在插件类中对应的属性名,description
则是描述信息 - help-属性名.html 帮助视图html片段
这就是Jenkins中的三种视图,更多关于Jelly的视图控件可以查看Jenkins控件
另外:
- Jenkins插件管理页面中,关于插件介绍的内容在index.jelly视图中进行更改
- 插件名称在pom.xml中更改name元素即可
配置文件与多语言国际化
-
config.jelly视图和global.jelly视图
在resources中创建对应的properties文件,比如config.properties对应的config.jelly视图的配置文件(这个是默认英文版本的),其他语言版本,使用config_语言编码.properties文件,比如简体中文,则使用config_zh_CN.properties配置文件,global.jelly视图类似。
格式使用属性名=数据
的方式,如下图所示:
使用的时候,则遵照${%属性名}的格式
help帮助视图
help帮助视图多语言,采用的是help-属性名_语言编码.html的方式-
插件类多语言
创建Messages.properties配置的方式来实现,其他语言如上面两种情况一样,使用Messages_语言编码.properties的方式实现。Jenkins使用Localizer生成Messages类,能够以类型安全的方式访问Message资源。src/main/resources/**/Messages.properties匹配的所以文件都会生成一个对应的Messages类。如过IDE找不到这些类,需要手动将target/generated-sources/localizer目录加入源码的根目录。返回用于显示的字符串的代码(如Descriptor.getDisplayName())可以使用Messages类获取本地化的消息。在运行时,适当的locale会被自动选择。
典型的工作流如下:
- 确定需要本地化的Messages
- 将消息写入Messages.properties文件,即可以为每个package编写一个,也可以整个module或者plugin只用一个
- 运行mvn compile生成Messages.java
- 更新代码,使用最新生成的消息格式方法读取
代码中则可以通过如下的方式进行引用
@Override
public String getDisplayName() {
return Messages.TelegramPublisher_DescriptorImpl_DisplayName();
}
国际化更多内容,请查看Jenkins官方文档
参数检查
Jenkins中进行字段检验,是通过创建doCheck字段名称()
这样的方法来处理的,value参数,则代表该字段的值。
///校验botUserName参数
public FormValidation doCheckBotUserName(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error(Messages.TelegramPublisher_DescriptorImpl_errors_missingBotUserName());
return FormValidation.ok();
}
///校验botToken参数
public FormValidation doCheckBotToken(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error(Messages.TelegramPublisher_DescriptorImpl_errors_missingBotToken());
return FormValidation.ok();
}
这样的话,当我们在全局配置中,未填写botUserName和botToken就会收到相应提示了。
打包上传
使用指令mvn package
进行打包,完成后会在target目录下生成hpi文件。
有两种方式上传插件到Jenkins
- 将hpi文件放到Jenkins下的plugins目录下
-
通过Jenkins的插件管理中心,进行手动上传hpi文件,具体操作步骤如下图所示:
本文对应的源代码,请移步Jenkins Telegram通知插件