来到新公司不久,主管安排一个jenkins 插件开发的小需求给我,让我练练手,之前从未接触过相关内容,一切从0开始,做了一个月,基本完成需求上的功能,期间遇到不少困难,记录做以总结。
现阶段网上相关的指导还是比较匮乏,我个人觉得比较好的方法是:参考已有插件的源码!
需求大致是这样的:
点击进入某次历史编译,将本次上传至Artifactory的文件copy 到Artifactory中的release目录下,目的是可以选择性的选择某次编译生成的文件copy到release目录下供测试的同事进行测试。
1、 开发环境的搭建
包括本地JDK、maven、eclipse等,参考链接:
https://jenkins.io/doc/developer/tutorial/prepare/
其中遇到的问题有:
公司的网络需要代理访问外网:(没有外网访问问题的可以忽略)
https://blog.csdn.net/u010531676/article/details/54343845
maven/conf/setting.xml中添加:
true
https
username
password
company host
888
www.google.com|*.somewhere.com
2、 创建一个空的plugin工程,调试以及生成插件
先是根据官方文档创建了一个空的工程,当时很费解,为什么我的工程里面没有HelloWorldBuilder.java文件?网上一些教程里面都是创建后就有这个文件的,因为是小白,这个东西当时纠结了好几个小时,后面尝试后才知道官方文档给出的是:
mvn archetype:generate -Dfilter=io.jenkins.archetypes:empty-plugin
默认为空的工程
可以使用:
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:
然后选择是空的工程还是hello_world工程,新手建议先通过hello_world工程了解工程结构,真正写项目建议基于empty-plugin。
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): : 4
调试插件:mvn hpi:run
访问http://localhost:8080/jenkins 可查看插件效果
生成插件:mvn package
插件为hpi格式,会保存在target/目录下,也可以手动安装hpi文件查看插件效果
参考链接:
https://wiki.jenkins.io/display/JENKINS/Plugin+tutorial
https://jenkins.io/doc/developer/tutorial/create/
3、 jenkins plugin 目录结构
这块网上的说明相对还是比较多的,引用网上给出的:
4、Plugin UI之configure/General中添加参数视图
jenkins插件的UI(界面)是通过与java文件一一对应的jelly文件去体现的,举个例子:
在官方给的helloworld工程中:
src/main/java/org/sample/HelloWorldBuilder.java
src/main/resources/org/sample/HelloWorldBuilder/config.jelly
两者是一一对应的,其中config.jelly用于工程相关参数配置,如果换成global.jelly则用于全局参数配置
一个jenkins build 的过程一般包括:
pipeline job往往以上过程全部在pipeline code中去实现,本次需求是要求在pipeline运行结束后执行copy操作,继承类似Builder,Recorder等构建中的扩展类是不能满足需求的。
后来把目标放在了jenkins的configure中的General 上,其中的选项可以和构建中无关。通过已有插件的源码,找到了JobProperty 类。效果如下:
java部分代码部分如下:
package io.jenkins.plugins.sample;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import net.sf.json.JSONObject;
public class MyJobProperty extends JobProperty> {
private final String yourname;
@DataBoundConstructor //构造函数需要添加DataBoundConstructor标记
public MyJobProperty(String yourname) {
this.yourname = yourname;
}
public String getYourname() {//和jelly中的field="yourname"相关联
return yourname;
}
@Extension //扩展标记
public static final class DescriptorImpl extends JobPropertyDescriptor {
//Descriptor 及其各种延伸的Descriptor ,例如:JobPropertyDescriptor,BuildStepDescriptor等,
//往往继承该类需要加上@Extension 用来告诉jenkins是JobPropertyDescriptor的扩展,需要创建对应的instance对象,已经对参数的校验。
@Override
public JobProperty> newInstance(StaplerRequest req, JSONObject formData) throws FormException {
//满足某种条件后创建MyJobProperty 对象
MyJobProperty jp = req.bindJSON(MyJobProperty.class, formData.getJSONObject("myjobproperty"));
if (jp == null) {
return null;
}
return jp;
}
@Override
public boolean isApplicable(Class extends Job> jobType) {
//是否对所有项目类型可用
return super.isApplicable(jobType);
}
@Override
public String getDisplayName() {
//本例中并未用到,在例如Builder类型的插件,添加构建过程的名称
return "MyJobProperty";
}
}
}
jelly部分代码如下:
5、Plugin UI之主面板和侧边栏部分
Action类可以在jenkins中的主面板中添加视图效果,其中的重写方法为侧边栏图标、名称、以及url名称
@Override
public String getIconFileName() {
return "document.png";
}
@Override
public String getDisplayName() {
return "MyJobProperty";
}
@Override
public String getUrlName() {
return "myjobproperty";
}
这里需要注意的是getUrlName() 返回值不能有空格,我在插件调试过程中出现了侧边栏不能点击的问题,就是因为返回值中有空格导致的。
jenkins本身提供了很多Action接口,例如:BuildBadgeAction(可以在build history中添加文字或者图片标签),RootAction(入口为jenkins的根目录)等,可以根据自己的需要实现对应的接口。
Action与build相关联一般有两种方式:
首先需要在action中的构造函数中添加Run/AbstractBuild/WorkflowRun作为传入参数,然后使用下列方法使二者关联。
第一种:addAction()
示例1:
@Override
public boolean prebuild(AbstractBuild,?> build, BuildListener listener) {
build.addAction(new MyBuildAction(build));
return true;
}
第二种:ActionFactory类
在MyAction类中添加内部类MyActionFactory
示例2:
@Extension
public static class MyActionFactory extends TransientActionFactory {
//本次需求中是要兼容pipeline类型,使用了WorkflowRun
@Override
public Class type() {
return WorkflowRun.class;
}
@Override
public Collection extends Action> createFor(WorkflowRun target) {
MyJobProperty prop = target.getParent().getProperty(MyJobProperty.class);
if (prop == null || target.getResult() != Result.SUCCESS) {
return Collections.emptySet();
} else {
return Collections.singleton(new MyAction(prop, target));
}
}
}
示例1和示例2不是同一个例子,本次项目使用的是示例2中方式,项目中用到了类似MyJobProperty 中的方法和参数,所以将MyJobProperty 作为了传入参数,具体需要根据需求定义参数。
具体界面还是使用jelly文件去实现,这里需要注意的是:
如果Action中有按钮点击事件,该如何实现?
jelly代码:
//action中的值需要和java代码一一对应,action="submit"则java中为doSubmit方法
java代码部分:
@RequirePOST
public void doSubmit(StaplerRequest req, StaplerResponse rsp) throws Exception {
JSONObject form = req.getSubmittedForm();
name = Util.fixEmpty(form.getString("name")); // 取feild中的值
}
如果要在编译历史中加入标记,该如何处理?
自定义Action类实现BuildBadgeAction,在对应的resource目录下新增badge.jelly文件
MyAction.java code:
public class MyAction implements BuildBadgeAction {
****
}
badge.jelly code:
<--! it.xxx 表示MyAction类中的getXxx()或者isXxx()方法的返回值 -->
Baidu
至此,界面的效果差不多是这样:
pipeline job在configure/General选择MyJobProperty,填好相关参数后保存退出,在build history中选择某次编译成功的条目,进入后可以看到侧边栏图标,点击侧边栏图标可以显示Action主界面,点击自定义按钮后执行业务逻辑,同时该build history中该次条目上添加标记。
6、其他注意点
1)点击按钮后刷新页面
run.replaceAction(this);
rsp.sendRedirect("");
2)字符串如果有跨行,如何在jenkins主界面能跨行显示
在jelly文件中如果使用 或者
等标签,在jenkins上不能正常显示跨行,需要使用
标签
'''
nihao
hello
'''
3)Action显示系统侧边栏
MyAction.java code:
public Run getOwner() {
return run;
}
index.jelly code:
4)configure/General中参数选中后不能保存,退出再进去后需要重新填写
原因:jelly文件中的feild名称和java文件中不匹配导致
5)侧边栏图标没有点击效果
原因:getUrlName的返回值不能有空格
6)pom.xml文件如何添加需要依赖项?
如果需要依赖A,并且有A的源码,可以先查看A的pom.xml文件,依次找到groupId,artifactId,version,然后在自己的pom.xml文件的dependencies标签中添加。
org.apache.httpcomponents
httpclient
4.5.5
7)如何下载已有插件源码?
Jenkins-插件管理-搜索需要的插件-点击插件名-点击github
8)jelly文件中一些关键字的含义
9)Node下workspace 获取,FilePath类型,没有上下文的情况下,即非构建中。
FilePath path = FilePathUtils.find(String nodename, String workspace);
7、Jenkins插件各个类或接口的含义
1)EnvironmentContributor
用来提供环境变量,重写buildEnvironmentFor函数,job可以获取JobProperty
例如:pipeline中echo ${MY_ENV} 会得到"I am local.prop"
@Extension
public class MyEnvVarsContributor extends EnvironmentContributor {
@Override
public void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener)
throws IOException, InterruptedException {
MyJobProperty jb = (MyJobProperty) j.getProperty(MyJobProperty.class);
if (jb != null) {
envs.put("MY_ENV", "I am local.prop");
}
super.buildEnvironmentFor(j, envs, listener);
}
}
2)JobProperty
配置中General部分,也可以用来设置参数
常见获取方式:
MyJobProperty prop = run.getParent().getProperty(MyJobProperty.class);
3)BuildBadgeAction
实现该接口的类配合badge.jelly文件可以在build history中显示标签
4)Descriptor
Descriptor 及其各种延伸的Descriptor ,例如:JobPropertyDescriptor,BuildStepDescriptor等
往往继承该类需要加上@Extension 用来告诉jenkins是JobPropertyDescriptor的扩展,需要创建对应的instance对象。
Descriptor 种类:https://javadoc.jenkins.io/hudson/model/Descriptor.html
5)AbstractStepDescriptorImpl
AbstractStepDescriptorImpl 是pipeline step的扩展,可以定义一个插件的function名称,例如:
继承之后重写以下两个方法:
@Override
public String getFunctionName() {//方法名,可以在pipeline语句中调用,其中的参数是
//继承AbstractStepImpl类的构造方法中传入。
return "publishHTML";
}
@Override
public String getDisplayName() {
return "Publish HTML reports";
}
6)Execution
例如:AbstractSynchronousNonBlockingStepExecution。继承后会重写run方法,里面是pipeline 中调用方法的具体内容。继承AbstractStepDescriptorImpl 的类会在其构造函数中调用关联Execution ,并调用run方法。例如:
public DescriptorImpl() {
super(PublishHTMLStepExecution.class);
}
7)EnvVars(待补充)
8)ActionFactory(待补充)
9)Context(待补充)