Jenkins 插件开发记录
来到新公司不久,主管安排一个jenkins 插件开发的小需求给我,让我练练手,之前从未接触过相关内容,一切从0开始,做了一个月,基本完成需求上的功能,期间遇到不少困难,记录做以总结。现阶段网上相关的指导还是比较匮乏,我个人觉得比较好的方法是:参考已有插件的源码!需求大致是这样的:点击进入某次历史编译,将本次上传至Artifactory的文件copy 到Artifactory中的release...
来到新公司不久,主管安排一个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中添加:<proxies> <proxy> <active>true</active> <protocol>https</protocol> <username>username</username> <password>password</password> <host>company host</host> <port>888</port> <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts> </proxy> </proxies>
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 目录结构
这块网上的说明相对还是比较多的,引用网上给出的:
- pom.xml - Maven POM 文件,用于配置插件的设定,包括插件所依赖的架包、JDK版本、插件名称和描述等。
- src/main/java - 插件的 Java 源文件
- src/main/resources - 插件的 Jelly 视图文件
- src/main/webapp - 插件的静态资源,如图片或 HTLM 等,本次项目中没有使用到。
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 的过程一般包括:
- Source Code Management
- Build Triggers
- Build Environment
- Build
- Build Environment
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<Job<?, ?>> {
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部分代码如下:
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:optionalBlock name="myjobproperty" title="click and input your name" checked="${!empty(instance)}">
<f:entry title="your name " field="yourname">
<f:textbox />
</f:entry>
</f:optionalBlock>
</j: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<WorkflowRun> {
//本次需求中是要兼容pipeline类型,使用了WorkflowRun
@Override
public Class<WorkflowRun> 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方法
<f:form action="submit" method="POST">
<f:bottomButtonBar>
<f:submit value="click"/>
</f:bottomButtonBar>
</f:form>
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:
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:if test="${it.isclick}"> <--! it.xxx 表示MyAction类中的getXxx()或者isXxx()方法的返回值 -->
<a href="www.baidu.com">Baidu</a>
</j:if>
</j:jelly>
至此,界面的效果差不多是这样:
pipeline job在configure/General选择MyJobProperty,填好相关参数后保存退出,在build history中选择某次编译成功的条目,进入后可以看到侧边栏图标,点击侧边栏图标可以显示Action主界面,点击自定义按钮后执行业务逻辑,同时该build history中该次条目上添加标记。
6、其他注意点
1)点击按钮后刷新页面
run.replaceAction(this);
rsp.sendRedirect("");
2)字符串如果有跨行,如何在jenkins主界面能跨行显示
在jelly文件中如果使用 <h1 />
或者 <p />
等标签,在jenkins上不能正常显示跨行,需要使用<pre />
标签
<pre >
'''
nihao
hello
'''
</pre >
3)Action显示系统侧边栏
MyAction.java code:
public Run getOwner() {
return run;
}
index.jelly code:
<st:include page="sidepanel.jelly" it="${it.owner}"/>
4)configure/General中参数选中后不能保存,退出再进去后需要重新填写
原因:jelly文件中的feild名称和java文件中不匹配导致
5)侧边栏图标没有点击效果
原因:getUrlName的返回值不能有空格
6)pom.xml文件如何添加需要依赖项?
如果需要依赖A,并且有A的源码,可以先查看A的pom.xml文件,依次找到groupId,artifactId,version,然后在自己的pom.xml文件的dependencies标签中添加。
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
</dependencies>
7)如何下载已有插件源码?
Jenkins-插件管理-搜索需要的插件-点击插件名-点击github
8)jelly文件中一些关键字的含义
- app Jekins实例
- instance 正在被配置的对象
- descriptor 对应于instance的Descriptor对象
- h hudson.Functions的实例,一个全局的工具类,提供静态工具方法
- it 当前UI所属的模型对象
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(待补充)
更多推荐
所有评论(0)