Hudson插件开发简介

2 篇文章 0 订阅

转:http://blog.csdn.net/littleatp2008/article/details/7001793


近期接触到Hudson的插件开发,觉得还是比较好玩的,但目前这方面的资料而非常之少,于是将自己一些学习资料简单归纳了一下,算是抛砖引玉吧

一、关于Hudson(又名Jenkins)

     简单说,它就是一个纯java实现开源的持续集成软件,一般搭载在web容器上用,有单独war包的形式,也有内嵌jetty服务器的安装包。在持续集成领域中相当出名,而其中最大的因素则源自其可伸缩的插件机制和强大的插件支持,目前已有超过400多款支持不同持续集成特性的免费可用插件。Hudson的插件机制允许开发者通过定制来做很多事情,包括自定义构建步骤、结果的展示方式、通知方式、与SCM系统的集成、测试和分析等等。

二、插件开发

    Hudson是基于maven的项目,其插件开发也离不开maven的支持,因此有必要稍微了解下maven是怎么用的:http://maven.apache.org/ 。此外,hudson提供了hpi插件来实现其插件开发。The Hudson HPI (Hudson Plug-in Interface) tool, 是一个Maven插件,可帮助开发者创建、构建、运行和调试Hudson插件项目

     安装完maven之后便可以开始玩了:

    1    创建项目

           找一个干净的地方,执行一下:mvn  hpi:create   此时maven会检查当前是否安装了hpi插件(hudson插件开发的maven插件,全称为hudson plugin Interface),如果没有将先下载安装;如果报错提示 无法识别 hpi命令或别名,那是maven找不到插件了,打开maven的setting.xml文件,添加maven插件查找路径:

  1. <pluginGroups>  
  2.     <pluginGroup>org.jenkins-ci.tools</pluginGroup>  
  3. </pluginGroups>  
<pluginGroups>
    <pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
创建项目成功之后,一个helloworld的骨架项目结构如下:
  1. pom.xml -       Maven POM file which is used to build your plugin  
  2. src/main/java - java源文件  
  3. src/main/resources - 插件的Jelly 视图文件  
  4. src/main/webapp - 插件的静态资源 such as images and HTML files.  
pom.xml -       Maven POM file which is used to build your plugin
src/main/java - java源文件
src/main/resources - 插件的Jelly 视图文件
src/main/webapp - 插件的静态资源 such as images and HTML files.
这是一标准的maven项目结构,紧接着执行一下打包试试: mvn package,在target目录下发现插件打成了jar包,另外还有一个hpi文件。而hpi文件便是hudson的标准插件格式,可以直接安装到已运行的hudson程序中(系统设置-插件-高级-上传插件)

此后执行hpi:run 可以开启一个test模式的hudson,其内置安装了当前开发中的插件,通过localhost:8080可以访问。hpi:run 命令包含了几个子task:启动jetty服务器,添加hudson为web项目、安装当前插件。 

  1. 插件的work子目录成为了当前Hudson的Home目录,work/plugins子目录则包含了一些hpi文件(对应于当前hudson中的插件列表);仔细点可以发现当前的目录中  
  2. 有一个hpl为后缀的文件,其对应了当前的helloworld插件项目;这是一个简单的文本文件,其内部描述了与当前项目构建相关的文件(包括classes、jars和resources)每次执行hpi:run命令时,HPI工具都会生成该文件,而Hudson解释该文件并直接加载该插件(而不需要把插件打成hpi的包)  
  3. 此种方式也方便于部署期间的调试。  
插件的work子目录成为了当前Hudson的Home目录,work/plugins子目录则包含了一些hpi文件(对应于当前hudson中的插件列表);仔细点可以发现当前的目录中
有一个hpl为后缀的文件,其对应了当前的helloworld插件项目;这是一个简单的文本文件,其内部描述了与当前项目构建相关的文件(包括classes、jars和resources)每次执行hpi:run命令时,HPI工具都会生成该文件,而Hudson解释该文件并直接加载该插件(而不需要把插件打成hpi的包)
此种方式也方便于部署期间的调试。

    2   扩展功能

         生成的helloworld项目默认添加了一个Builder的扩展类(名为HelloWorldBuilder)。Hudson的扩展机制与Eclipse有些相似,也有扩展点和扩展的概念,扩展点即是一组接口,其允许第三方开发者实现该接口(提供扩展实现)来增强系统的功能。下面的应用将围绕HelloWorldBuilder进行说明:

  1. <strong>一次构建过程通常包括:</strong>  
  2.    SCM checkout - check out出源码  
  3.    Pre-build    - 预编译  
  4.    Build wrapper  -准备构建的环境,设置环境变量等  
  5.    Builder runs   - 执行构建,比如调用calling Ant, Make 等等  
  6.    Recording    - 记录输出,如测试结果  
  7.    Notification    - 通知成员  
<strong>一次构建过程通常包括:</strong>
   SCM checkout - check out出源码
   Pre-build    - 预编译
   Build wrapper  -准备构建的环境,设置环境变量等
   Builder runs   - 执行构建,比如调用calling Ant, Make 等等
   Recording    - 记录输出,如测试结果
   Notification    - 通知成员

 jenkins构建器的扩展点通过Builder接口声明,在默认情况下,jenkins自带了Ant和Maven的builder扩展实现(新建一个job,可以添加ant build step...)

生成的HelloWorld类如下:

  1. public class HelloWorldBuilder extends Builder {  
  2.    //构建的执行通过实现perform方法来进行自定义  
  3.     public boolean perform(AbstractBuild<?> ab, Launcher launcher, BuildListener bl)   
  4.                                              throws InterruptedException, IOException;{  
  5.     ..}  
  6.   
  7.    /* 
  8.      Build参数是描述了当前任务的一次构建,通过它可以访问到一些比较重要的模型对象如: 
  9.         1 Project    当前项目的对象 
  10.        2 workspace  构建的工作空间 
  11.         3 Result    当前构建步骤的结果 
  12.      Launcher 用于启动构建 
  13.      BuildListener  该接口用于检查构建过程的状态(开始、失败、成功..) 
  14.         通过它可以在构建过程中发送一些控制台信息给Hudson 
  15.     */  
  16.      perform方法的返回值告诉jenkins当前步骤是否成功,如果失败了Hudson将放弃后续的步骤。  
  17.      此外有一个内部静态类,该类通过@Extension声明告诉Hudson,这是一个扩展实现  
  18.      @Extension // This indicates to Jenkins that this is an implementation of an extension point.  
  19.       public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {  
  20.             public boolean isApplicable(Class<? extends AbstractProject> aClass) {  
  21.             // 是否对所有项目类型可用  
  22.             return true;  
  23.              }<p>          /** 
  24.            * builder的显示名. 
  25.            */  
  26.           public String getDisplayName() {  
  27.               return "Say hello world";  
  28.           }  
  29.       }</p>}  
public class HelloWorldBuilder extends Builder {
   //构建的执行通过实现perform方法来进行自定义
    public boolean perform(AbstractBuild<?> ab, Launcher launcher, BuildListener bl) 
                                             throws InterruptedException, IOException;{
    ..}

   /*
     Build参数是描述了当前任务的一次构建,通过它可以访问到一些比较重要的模型对象如:
        1 Project    当前项目的对象
       2 workspace  构建的工作空间
        3 Result    当前构建步骤的结果
     Launcher 用于启动构建
     BuildListener  该接口用于检查构建过程的状态(开始、失败、成功..)
        通过它可以在构建过程中发送一些控制台信息给Hudson
    */
     perform方法的返回值告诉jenkins当前步骤是否成功,如果失败了Hudson将放弃后续的步骤。
     此外有一个内部静态类,该类通过@Extension声明告诉Hudson,这是一个扩展实现
     @Extension // This indicates to Jenkins that this is an implementation of an extension point.
      public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
            public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // 是否对所有项目类型可用
            return true;
             }<p>          /**
           * builder的显示名.
           */
          public String getDisplayName() {
              return "Say hello world";
          }
      }</p>}

 关于构建方法(Perform)的一个实现样例:

  1.  List<Cause> buildStepCause = new ArrayList();  
  2.  buildStepCause.add(new Cause() {  
  3.    public String getShortDescription() {  
  4.      return "Build Step started by Hello Builder";  
  5.    }  
  6.  });  
  7.  listener.started(buildStepCause);     //向hudson控制台输出日志  
  8.    
  9.  ArgumentListBuilder args = new ArgumentListBuilder();  
  10.  if (launcher.isUnix()) {  
  11.    args.add("/bin/ls");  
  12.    args.add("-la");  
  13.  } else {  
  14.    args.add("dir"); //Windows  
  15.  }  
  16.  String homeDir = System.getProperty("user.home");  
  17.  args.add(homeDir);  
  18.  try {  
  19.    int r;  
  20.    //调用外部命令,cmds传入命令和参数;stdout方法将标准输出重定向到listener的流中(输出到hudson的web控制台),join等待完成并返回结果  
  21.    //可以看到launcher是相当强大的..   
  22.    r = launcher.launch().cmds(args).stdout(listener).join();  
  23.   
  24.    if (r != 0) {  
  25.      listener.finished(Result.FAILURE);  
  26.      return false;  
  27.    }  
  28.  } catch (IOException ioe) {  
  29.    ioe.printStackTrace(listener.fatalError("Execution" + args + "failed"));  //打印异常,标记结果  
  30.    listener.finished(Result.FAILURE);             
  31.    return false;  
  32.  } catch (InterruptedException ie) {  
  33.    ie.printStackTrace(listener.fatalError("Execution" + args + "failed"));  
  34.    listener.finished(Result.FAILURE);  
  35.    return false;  
  36.  }  
  37.   
  38.  listener.finished(Result.SUCCESS);  
 List<Cause> buildStepCause = new ArrayList();
 buildStepCause.add(new Cause() {
   public String getShortDescription() {
     return "Build Step started by Hello Builder";
   }
 });
 listener.started(buildStepCause);     //向hudson控制台输出日志
 
 ArgumentListBuilder args = new ArgumentListBuilder();
 if (launcher.isUnix()) {
   args.add("/bin/ls");
   args.add("-la");
 } else {
   args.add("dir"); //Windows
 }
 String homeDir = System.getProperty("user.home");
 args.add(homeDir);
 try {
   int r;
   //调用外部命令,cmds传入命令和参数;stdout方法将标准输出重定向到listener的流中(输出到hudson的web控制台),join等待完成并返回结果
   //可以看到launcher是相当强大的.. 
   r = launcher.launch().cmds(args).stdout(listener).join();

   if (r != 0) {
     listener.finished(Result.FAILURE);
     return false;
   }
 } catch (IOException ioe) {
   ioe.printStackTrace(listener.fatalError("Execution" + args + "failed"));  //打印异常,标记结果
   listener.finished(Result.FAILURE);           
   return false;
 } catch (InterruptedException ie) {
   ie.printStackTrace(listener.fatalError("Execution" + args + "failed"));
   listener.finished(Result.FAILURE);
   return false;
 }

 listener.finished(Result.SUCCESS);

     3   添加配置

     Jenkins使用了Jelly页面渲染技术,这是一个基于XML的服务端页面渲染引擎,其将基于Jelly的xml标签转换为对应的Html标签并输出到客户端。模型对象的信息通过Jexl表达式被传递到页面上(相当于Jsp的JSTL)。jelly文件以.jelly为后缀,在hudson中使用类全名的形式来查找模型类对应的jelly页面文件,如名为org.sample.hudson.HelloWorldBuilder的类,其对应的页面文件应该存在于resource目录的以下位置中(以classpath为根)

  1. org/sample/hudson/HelloWordBuilder  
org/sample/hudson/HelloWordBuilder

此外hudson通过固定的命名方式来确定页面文件属于局部配置还是全局配置:config.jelly提供局部配置;global.jelly提供全局配置

     A  局部配置详解

         config.jelly 的内容将被包含在扩展功能的配置中
                以HelloWorldBuilder为例,其扩展的是一个Hudson Job的构建步骤,那么config.jelly  提供的便是该构建步骤对应的配置内容
   样例说明:

  1. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"   
  2. xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">  
  3.    <f:entry title="名称" field="name">  
  4.          <f:textbox />  
  5.   </f:entry>  
  6. </j:jelly>  
<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="名称" field="name">
         <f:textbox />
  </f:entry>
</j:jelly>

   entry 表示用于交互的html表单域,title将作为表单域label的值
   textbox 表示简单的渲染一个text
   允许为表单域增加帮助说明(在页面上对应于文本框后面出现问号按钮,一点击可出现提示):
   在同名目录下创建help-{fileName}.html,在该文件中添加帮助内容;帮助内容允许是动态的,即可以从模型中拉取信息进行显示,这需要将html后缀改为jelly,而文件的形式大致如下:

  1. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">  
  2.   <div>  
  3.    Welcome to ${app.displayName}. //应用的Display名称(一般就是Hudson)  
  4.    Enter your name in the Field.  
  5.   </div>  
  6. </j:jelly>  
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
  <div>
   Welcome to ${app.displayName}. //应用的Display名称(一般就是Hudson)
   Enter your name in the Field.
  </div>
</j:jelly>

jelly后缀的文件在渲染时会交给jelly引擎执行,因而支持动态显示能力。
jexl表达式替换模型数据的规则:${modelName.attrName}  调用对应模块的get**方法获得值
关于内置模型对象的说明:

  1. 1  app  Hudson应用程序对象 如上面的displayName例子  
  2. 2  it   当前UI所属的模型对象,在上面的Builder扩展例子中则对应于HelloWorldBuilder对象  
  3.        ${it.name} 对应于builder的getName()方法  
  4. 3  h   一个全局的工具类,提供静态工具方法     
1  app  Hudson应用程序对象 如上面的displayName例子
2  it   当前UI所属的模型对象,在上面的Builder扩展例子中则对应于HelloWorldBuilder对象
       ${it.name} 对应于builder的getName()方法
3  h   一个全局的工具类,提供静态工具方法   

    通过Job配置界面保存之后,hudson会创建builder对象,并将表单值通过构造器注入,构造器声明如下:

  1. @DataBoundConstructor  
  2. public HelloWorldBuilder(String name) {  
  3.    this.name = name;  
  4. }  
@DataBoundConstructor
public HelloWorldBuilder(String name) {
   this.name = name;
}


而builder必须提供getName方法,这样可将配置到config.xml中(hudson使用xml存储配置信息)在重新打开job配置时可自动填值

  关于表单值的校验,以文本域name为例:
  jelly在渲染时自动增加了ajax校验的功能脚本,于是文本框失去焦点时会往服务端发送校验请求:

[javascript] view plain copy print ?
  1. GET /job/TestProject/descriptorByName/org.sample.hudson.HelloWorldBuilder/checkName?value=xy HTTP/1.1  
  2. Host: localhost:8080  
  3. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:6.0.1) Gecko/20100101 Firefox/6.0.1  
  4. Accept: text/javascript, text/html, application/xml, text/xml, */*  
GET /job/TestProject/descriptorByName/org.sample.hudson.HelloWorldBuilder/checkName?value=xy HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:6.0.1) Gecko/20100101 Firefox/6.0.1
Accept: text/javascript, text/html, application/xml, text/xml, */*

此后Hudson找到HelloWorldBuilder,查找doCheckName方法(在Descriptor类中查找),如果没有找到则忽略该请求
否则返回检查结果在前端展示。doCheckName方法的样例实现

  1. public FormValidation doCheckName(@QueryParameter String value)  //@QueryParameter注解表示注入http请求参数  
  2.                                       throws IOException, ServletException {  
  3.         if (value.length() == 0) {  
  4.             return FormValidation.error("Please set a name");  
  5.         }  
  6.         if (value.length() < 4) {  
  7.             return FormValidation.warning("Isn't the name too short?");  
  8.         }  
  9.         return FormValidation.ok();  
  10. }  
public FormValidation doCheckName(@QueryParameter String value)  //@QueryParameter注解表示注入http请求参数
                                      throws IOException, ServletException {
        if (value.length() == 0) {
            return FormValidation.error("Please set a name");
        }
        if (value.length() < 4) {
            return FormValidation.warning("Isn't the name too short?");
        }
        return FormValidation.ok();
}

  B  全局配置详解

         如上所述,global.jelly 为全局配置页面,示例:

  1. <f:section title="Hello World Builder">  
  2.    <f:entry title="French" description="Check if we should say hello in French"  
  3.      help="/plugin/javaone-sample/help-globalConfig.html">  
  4.       <f:checkbox name="hello_world.useFrench" checked="${descriptor.useFrench()}" />  
  5.    </f:entry>  
  6. </f:section>  
  7. //在jenkins的系统设置中可以找到相应的配置段落。  
<f:section title="Hello World Builder">
   <f:entry title="French" description="Check if we should say hello in French"
     help="/plugin/javaone-sample/help-globalConfig.html">
      <f:checkbox name="hello_world.useFrench" checked="${descriptor.useFrench()}" />
   </f:entry>
</f:section>
//在jenkins的系统设置中可以找到相应的配置段落。

其中${descriptor.useFrench()} 调用builder的(getDescriptor)得到Descriptor对象,调用其useFrench方法进行取值;
help声明了帮助内容文档位置;
在每次保存全局配置时,jenkins都会调用调用该descriptor对象,并调用其configure方法,可以实现该方法并提供自己的定制:

  1. public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {  
  2.        useFrench = formData.getBoolean("useFrench");  
  3.        save();  
  4.        return super.configure(req,formData);  
  5.  }  
  6. //save方法用于将当前Descriptor所提供的配置持久化(通过get**方法)  
  7. //load方法用于将持久化的信息注入到当前Descriptor中(通过set**方法)  
  8. 因此为了使save和load能正常工作,需要提供配置项的get/set方法  
  9. <strong>使用场景</strong>:在构造器方法中执行load,将全局配置诸如到当前对象中;配置文件通过表达式调用descriptor的get**方法显示到前端;在保存系统配置时,configure方法中将配置读入当前对象,并持久化。  
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
       useFrench = formData.getBoolean("useFrench");
       save();
       return super.configure(req,formData);
 }
//save方法用于将当前Descriptor所提供的配置持久化(通过get**方法)
//load方法用于将持久化的信息注入到当前Descriptor中(通过set**方法)
因此为了使save和load能正常工作,需要提供配置项的get/set方法
<strong>使用场景</strong>:在构造器方法中执行load,将全局配置诸如到当前对象中;配置文件通过表达式调用descriptor的get**方法显示到前端;在保存系统配置时,configure方法中将配置读入当前对象,并持久化。

 

三、其他资料

Hudson的扩展点JavaDoc:http://wiki.jenkins-ci.org/display/JENKINS/Extension+points

Hudson插件开发简单介绍:https://wiki.jenkins-ci.org/display/~martino/2011/10/27/The+JenkinsPluginTotallySimpelGuide

实现报告发布扩展(Publisher)的介绍:http://www.theserverlabs.com/blog/2008/09/24/developing-custom-hudson-plugins-integrate-with-your-own-applications/

一个Html报告发布扩展的例子(基于Selenium的报告发布扩展):

https://github.com/jenkinsci/seleniumhtmlreport/blob/master/src/main/java/org/jvnet/hudson/plugins/seleniumhtmlreport/SeleniumHtmlReportPublisher.java

Hudson插件大全介绍 - http://wiki.hudson-ci.org/display/HUDSON/All+Plugins+by+Topic


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值