插件开发环境搭建
首先需要安装:
maven3
jdk6+
安装完成后,修改maven目录下的settings.xml文件:
linux :
~/.m2/settings.xml
windows :
%USERPROFILE%.m2\setttings.xml
使用如下命令创建一个新的插件。
需要输入插件的groupId,artifactId, 然后会在当前目录创建一个jenkins插件的骨架目录(熟悉maven的同学知道这个一个标准的maven项目目录结构)。
插件目录结构
pom.xml: maven使用这个文件来构建插件,所有的插件都是基于Plugin Parent Pom。
src/main/java:java源码。
src/main/resources:jelly视图文件,用于在web界面上显示。
src/main/webapp: 静态的资源文件,例如图片和html文件。
导入到IDE:
intellij idea:直接在ide中导入pom文件就能导入。
eclipse:运行如下命令。
调试插件
linux
windows
输入命令过后可以打开浏览器,输入:http://localhost:8080/jenkins,就可以看见你的插件在jenkins中运行起来了,现在就可以开始进行调试了。
这会在window对象上绑定一个myClassName的实例,现在在入口文件里面可以调用这个实例里面的方法,或者值。
修改端口:
该命令会在target目录创建出 ‘插件名称’.hpi 文件,其他用户可以直接将这个插件上传安装到Jenkins中使用(或者放到$JENKINS_HOME/plugins目录中)。
Jenkins插件之HelloWorld
在之前我们使用mvn hpi:create创建插件目录时,Jenkins在我们的项目中生成了一个HelloWorldBuilder的插件,这是一个官方示例,下面带大家分析一下这个插件的示例源码。
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
}
首先创建一个类继承于Builder,代表使用这个插件是一个构建插件(如果继承于Scm,代表这个插件是一个源码插件,例如Git,Svn插件),然后实现SimpleBuildStep接口。
在Jenkins的插件中,每一个插件类中都必须要有一个Descriptor内部静态类,它代表一个类的’描述者‘,用于指明这是一个扩展点的实现,Jenkins是通过这个描述者才能知道我们自己写的插件 。
每一个‘描述者’静态类都需要被@Extension注解,Jenkins内部会扫描@Extenstion注解来知道注册了有哪些插件。
@Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { private boolean useFrench; public DescriptorImpl() { load(); } public boolean isApplicable(Class<? extends AbstractProject> aClass) { return true; } public String getDisplayName() { return "Say hello world"; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { save(); return super.configure(req,formData); } public boolean getUseFrench() { return useFrench; } } |
在Desciptor类中有两个方法需要我们必须要进行重写。
public boolean isApplicable(){ return true; } |
这个方法的返回值代表这个Builder在Project中是否可用,我们可以将我们的逻辑写在其中,例如判断一些参数,最后返回true或者false来决定这个Builder在此处是否可用。
public String getDisplayName(){ return "Say hello world"; } |
这个方法返回的是一个String类型的值,这个名称会用在web界面上显示的名称。
如果我们在插件中需要获取一些系统设置参数,我们可以在Descriptor中获取
一个参数对应Descriptor中的一个属性,其中的userFrench属性是一个全局配置,可以在系统设置里面看到这个属性。
private boolean useFrench; public DescriptorImpl() { load(); } |
在Descirptor构造函数中使用load()进行加载全局配置,然后我们就可以在插件中获取到配置信息。
@Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { useFrench = formData.getBoolean("useFrench"); save(); return super.configure(req,formData); } |
当在全局配置修改属性后,需要在configure()方法中调用save()将全局配置信息持久化到xml,我们可以在workspace的插件名.xml中看到持久化的数据。
在每个插件的perform()方法中,是perform真正开始执行的地方,我们如果要在插件中完成什么事,代码逻辑也是写在perform方法中,perform方法参数中build代表当前构建,workspace代表当前工作目录,通过workspace可以获取到当前工作目录的信息,并可以做些操作,如workspace.copyTo("/home"),launcher代表启动进程,可以通过launcher执行一些命令,如launcher.launch().stdout(listener).cmds("pwd").start();,listener代表一个监听器,可以将运行的内容信息通过listener输出到前台console output。
public class HelloWorldBuilder extends Builder implements SimpleBuildStep { @Override public void perform(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) { //yuor code... listener.getLogger().println("Hello World" + name); } } |
如上面的代码所示,在perform方法中我们通过listener打印了一行”Hello World“ + name,name是一个变量,这个变量的值从哪里来下面我会介绍一下给大家。在web界面上的控制台可以看见 Hello World kinder,而kinder这个值是由我们自己定义的。
在jenkins插件中,如果我们需要一些自定义的参数信息,如构建时执行一些命令,命令的内容是由用户输入,这个时候需要一个变量来记录用户输入的信息 。
所以在HelloWorkdBuilder中定义一个属性与用于输入的信息相对应,如上面的name属性。
public class HelloWorldBuilder extends Builder implements SimpleBuildStep { private final String name; .... } |
这个属性的值是在job的配置过程中输入,由Jenkins从web前端界面传递过来的值,我们还需要在HelloWorldBuilder的构造方法中进行参数的注入。
public class HelloWorldBuilder extends Builder implements SimpleBuildStep { private final String name; @DataBoundConstructor public HelloWorldBuilder(String name) { this.name = name; } |
类似于Spring的依赖注入,在这里Jenkins要求进行参数注入的构造方法需要用@DataBoundConstructor注解标注,以便Jenkins可以找到这个构造函数,并且调用这个构造函数,将web界面上配置的参数传递进HelloWorldBuilder,这样就可以在HelloWorldBuilder中使用这个属性了。
到此,这个插件的后台代码就已经搞定了,现在给大家讲讲怎么样编写这个前端配置的视图 。
Jenkins中的视图
Jenkins 使用jelly来编写视图,Jelly 是一种基于 Java 技术和 XML 的脚本编制和处理引擎。Jelly 的特点是有许多基于 JSTL (JSP 标准标记库,JSP Standard Tag Library)、Ant、Velocity 及其它众多工具的可执行标记。Jelly 还支持 Jexl(Java 表达式语言,Java Expression Language),Jexl 是 JSTL 表达式语言的扩展版本。Jenkins的界面绘制就是通过Jelly实现的。
在Jenkins 中的视图的类型有三种
global.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:section title="Hello World Builder"> <f:entry title="French" field="useFrench" description="Check if we should say hello in French"> <f:checkbox /> </f:entry> </f:section> </j:jelly> |
config.jelly Job的配置视图
<?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:entry title="Name" field="name"> <f:textbox /> </f:entry> </j:jelly> |
在定义一个属性时,使用<f:entry>标签代表这是一个属性,其中title是指在界面上显示的字段名,而field是指这个属性在HelloWorldBuilder中对应的属性名,jenkins通过这个名称来与HelloWorldBuilder中的属性相对应,从而使用@DataBoundConstructor标注的构造函数将这些变量注入到HelloWorldBuilder类中。
help-属性名.html 帮助视图 html片段
<div> Help file for fields are discovered through a file name convention. This file is help for the "name" field. You can have <i>arbitrary</i> HTML here. You can write this file as a Jelly script if you need a dynamic content (but if you do so, change the extension to <tt>.jelly</tt>). </div> |
这是Jenkins 中的三种视图,上面也介绍了两个简单的控件textbox和checkbox的使用,更多的关于Jelly的视图使用可以查看jelly官网。
Jenkins 数据持久化
我们之前在web界面上输入了name,这个信息在下一次构建的时候仍然存在,说明jenkins中需要使用数据持久化来将我们配置的信息保存下来,而Jenkins 使用文件来存储数据(所有数据都存储在$JENKINS_HOME),有些数据,比如 console 输出,会作为文本文件存储;大多数的结构数据,如一个项目的配置或构建(build)记录信息则会通过 XStream 持久化为一个xml文件,如下图所示
而在需要信息的时候,jenkins又从xml文件中读取到相应的数据,返回给应用程序。
总结
在本文,主要介绍了Jenkins的简单使用,以及Jenkins的插件开发环境和Jenkins插件结构等一些简单入门介绍,如果想要了解更多的关于Jenkins的东西,还是需要去看Jenkins的官方wiki, 上面有详细的关于每个扩展点已经Jenkins的api的使用介绍,同样,你也可以下载Jenkins的源码来查看内部的一些实现方式。
在Github Jenkinci也有很多的关于Jenkins插件的源码,我们可以通过源码了解一些扩展点是怎样使用,参照别人的源码来写出自己的插件。