jenkins插件开发
https://jenkins.io/doc/developer/
1. 向导
jenkins是一个强大的插件系统,开发者使用插件,几乎可以影响到所有的jenkins操作。这个章节演示了简单的插件功能。
1.1 准备插件的开发环境
安装JDK环境,安装maven,保证mvn路径被添加到path变量。
1.2 创建插件
打开命令行,进入我们希望的目录中,然后执行
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:plugin -DarchetypeCatalog=remote
运行结果
… 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:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.) Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 3 Choose io.jenkins.archetypes:hello-world-plugin version: 1: 1.1 2: 1.2 3: 1.3 Choose a number: 3: 3 [INFO] Using property: groupId = unused Define value for property artifactId: demo Define value for property version 1.0-SNAPSHOT: : [INFO] Using property: package = io.jenkins.plugins.sample Confirm properties configuration: groupId: unused artifactId: demo version: 1.0-SNAPSHOT package: io.jenkins.plugins.sample Y: : y
以上标出了关键的地方:
- 选择hello-word-plugin作为我们的插件结构
- 选择1.3最新版本
- artifactid是强制要求的,而且在jenkins中运行时要求唯一,我们设置为demo。
- 默认是 1.0-SNAPSHOT作为开发的版本号。(maven中的版本号区别可以查看: 这里 )
- 最后最列举出来的信息进行确认Y
以上会创建一个目录,名字与artifactid的值一致(这里的值是demo),而且里面有一些基本的可运行的插件结构。执行以下指令来校验这个插件源码是否可以运行:
$ mv demo demo-plugin $ cd demo-plugin $ mvn verify
这里第一步就是重命名这个文件夹,接着verify会下载一系列依赖,然后进入生命周期,包括FindBugs静态分析和测试,直到显示如下信息:
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 06:11 min [INFO] Finished at: 2017-03-02T14:14:34+01:00 [INFO] Final Memory: 73M/872M [INFO] ------------------------------------------------------------------------
1.3 构建和运行插件
maven的HPI插件用来构建和打包jenkins插件,这个插件提供了一个简单的方式来运行插件:
mvn hpi:run
以上指令会运行一个jenkins实例(这个jenkins实例的数据保存在插件目录的work目录下),在 http://localhost:8080/jenkins/,然后自动打开一个控制台(这个控制台对应jenkins实例),控制台显示
INFO: Jenkins is fully up and running
接着我们打开一个浏览器访问http://localhost:8080/jenkins/,在jenkins中创建一个自由风格的项目,在构建设置中,启用我们的插件“Say Hello Word”,在出现的选项中随便输入一个问候语“Hello Jenkins!”,然后保存,进行项目构建,打开这次构建的控制台,可以看到我们之前输入的值,被输出到控制台上了:
Started by user anonymous Building in workspace /Users/mrjenkins/demo/work/workspace/testjob Hello, Jenkins! Finished: SUCCESS
1.4 扩展插件
这次通过以下特征来扩展插件:
- 用合适的数据结构来保存我们的问候语,而不是仅仅在构建控制台中输出
- 添加一个新的页面来显示我们用过的问候语
1.4.1 记录问候语
首先,在HelloWorldBuilder所在的包中创建一个类HelloWorldAction:
package io.jenkins.plugins.sample; import hudson.model.Action; public class HelloWorldAction implements Action { @Override public String getIconFileName() { return null; } @Override public String getDisplayName() { return null; } @Override public String getUrlName() { return null; } }
Action都是jenkins的基础构建扩展单位,这些单位可以访问并且保存许多对象,而且还可以把这些对象显示到界面上。
我们往这个Action类中添加我们希望保存的数据(私有字段和对应的geter、setter):
(...) public class HelloWorldAction implements Action { private String name; public HelloWorldAction(String name) { this.name = name; } public String getName() { return name; } (...)
现在,需要我们在构建的时候,生成这个类的实例,在HelloWorldBuilder类的perform方法中,添加如下一行标注的代码:
(...) @Override public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { run.addAction(new HelloWorldAction(name)); if (useFrench) { listener.getLogger().println("Bonjour, " + name + "!"); } else { listener.getLogger().println("Hello, " + name + "!"); } } (...)
保存后,mvn hpi:run来运行插件。之后每次进行构建,上面的Action都会被应用。可以通过查看build.xml来确认,这个文件在这里work/jobs/JOBNAME/builds/BUILDNUMBER/。文件大致如下:
<build> <actions> <hudson.model.CauseAction> <causes> <hudson.model.Cause_-UserIdCause/> </causes> </hudson.model.CauseAction> <io.jenkins.plugins.sample.HelloWorldAction plugin="demo@1.0-SNAPSHOT"> <name>Jenkins</name> </io.jenkins.plugins.sample.HelloWorldAction> </actions> (...) </build>
对于以上三个标注的地方:
- 代表了构建的原因(如果触发这次构建)也被作为action来保存了,这里指出了是匿名用户触发的构建
- 这是我们新建的Action
- 这个name值就是我们在构建配置中设置的值
1.4.2 添加个页面来显示数据
首先,回到HelloWorldAction中修改如下函数的返回值:
@Override public String getIconFileName() { return "document.png"; } @Override public String getDisplayName() { return "Greeting"; } @Override public String getUrlName() { return "greeting"; }
分别代表了多出的侧边栏的图标、标题,以及地址(http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/)。
接着,以上地址的页面需要被配置。在jenkins中创建视图,通过Jelly来实现。
在src/main/resources/io/jenkins/plugins/sample/中创建一个目录,名为HelloWorldAction。这个目录对应我们的HelloWorldAction类,代表这个目录中的资源给对应的类来使用。目录中有如下资源:
- config.jelly:代表构建配置中的表单界面
- config*.properties:contain the localizations for the build step configuration
- help*.html:provide the localized inline help for the configuration
在以上目录(src/main/resources/io/jenkins/plugins/sample/HelloWorldAction/)中创建一个 index.jelly,这个界面会显示在http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/中。index.jelly内容如下:
<?jelly escape-by-default='true'?> <j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler"> <l:layout title="Greeting"> <l:main-panel> <h1> Name: ${it.name} </h1> </l:main-panel> </l:layout> </j:jelly>
对于以上四个标注的地方:
- layout标签是jenkins提供的一个基础界面容器,包含了header、footer、主内容区域,侧边栏等
- main-panel标签中的内容代表了容器中的主内容
- jelly中可以使用任意html标签
- 这是一个JEXL表达式,it代表了这个视图所属的Java对象(类似于this),在这个例子中就是指向了HelloWorldAction实例,it.name等价于调用了这个属性的getter,也就是调用了getName方法
做好以上配置后,当我们访问http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/时,jelly以及里面的name数据就显示出来了
1.4.3 为页面添加侧边栏
以上的显示的界面中没有侧边工具栏,这里我们要做的就是把侧边工具栏显示出来。要实现这个效果,
- 在Action中获取到这次构建的引用
- 从这个引用中获取到工具栏fragment
- 将这个fragment引用到我们的界面中
实现第一步,让HelloWorldAction来实现RunAction2这个接口,里面有两个方法:
- onAttached(Run):called when the run is first attached to a build
- onLoad(Run):called when the action and run are loaded from disk
(...) import hudson.model.Run; import jenkins.model.RunAction2; public class HelloWorldAction implements RunAction2 { private transient Run run; @Override public void onAttached(Run<?, ?> run) { this.run = run; } @Override public void onLoad(Run<?, ?> run) { this.run = run; } public Run getRun() { return run; } (...)
对于以上标注的两个个地方:
- 被这个关键字修饰的run,不会随着这个action序列化到硬盘上
- 使我们获取到的run,可以用在jelly视图中
然后在jelly中引入run提供的侧边工具栏:
(...) <l:layout title="Greeting"> <l:side-panel> <st:include page="sidepanel.jelly" it="${it.run}" optional="true" /> </l:side-panel> <l:main-panel> (...) </l:main-panel> </l:layout> (...)
类似于main-panel,side-panel代表了侧标栏。接着include标签,可以从这个标签的it属性所指向的对象中,引入page所指向的视图,效果就是从run中引入了sidepanel.jelly视图。然后将这个标签设置为可选的,这样一来,当sidepanel.jelly不存在时也不会报错了
以上配置后,http://JENKINS/job/JOBNAME/BUILDNUMBER/greeting/就有侧边栏了。