近期接觸到Hudson的插件開發,以爲仍是比較好玩的,但目前這方面的資料而很是之少,因而將本身一些學習資料簡單概括了一下,算是拋磚引玉吧javascript
1、關於Hudson(又名Jenkins)html
簡單說,它就是一個純java實現開源的持續集成軟件,通常搭載在web容器上用,有單獨war包的形式,也有內嵌jetty服務器的安裝包。在持續集成領域中至關出名,而其中最大的因素則源自其可伸縮的插件機制和強大的插件支持,目前已有超過400多款支持不一樣持續集成特性的免費可用插件。Hudson的插件機制容許開發者經過定製來作不少事情,包括自定義構建步驟、結果的展現方式、通知方式、與SCM系統的集成、測試和分析等等。前端
2、插件開發java
Hudson是基於maven的項目,其插件開發也離不開maven的支持,所以有必要稍微瞭解下maven是怎麼用的:http://maven.apache.org/ 。此外,hudson提供了hpi插件來實現其插件開發。The Hudson HPI (Hudson Plug-in Interface) tool, 是一個Maven插件,可幫助開發者建立、構建、運行和調試Hudson插件項目git
安裝完maven以後即可以開始玩了:github
1 建立項目web
找一個乾淨的地方,執行一下:mvn hpi:create 此時maven會檢查當前是否安裝了hpi插件(hudson插件開發的maven插件,全稱爲hudson plugin Interface),若是沒有將先下載安裝;若是報錯提示 沒法識別 hpi命令或別名,那是maven找不到插件了,打開maven的setting.xml文件,添加maven插件查找路徑:ajax
<pluginGroups> <pluginGroup>org.jenkins-ci.tools</pluginGroup> </pluginGroups>
建立項目成功以後,一個helloworld的骨架項目結構以下:apache
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項目、安裝當前插件。
插件的work子目錄成爲了當前Hudson的Home目錄,work/plugins子目錄則包含了一些hpi文件(對應於當前hudson中的插件列表);仔細點能夠發現當前的目錄中 有一個hpl爲後綴的文件,其對應了當前的helloworld插件項目;這是一個簡單的文本文件,其內部描述了與當前項目構建相關的文件(包括classes、jars和resources)每次執行hpi:run命令時,HPI工具都會生成該文件,而Hudson解釋該文件並直接加載該插件(而不須要把插件打成hpi的包) 此種方式也方便於部署期間的調試。
2 擴展功能
生成的helloworld項目默認添加了一個Builder的擴展類(名爲HelloWorldBuilder)。Hudson的擴展機制與Eclipse有些類似,也有擴展點和擴展的概念,擴展點便是一組接口,其容許第三方開發者實現該接口(提供擴展實現)來加強系統的功能。下面的應用將圍繞HelloWorldBuilder進行說明:
一次構建過程一般包括:
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類以下:
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; } /**
* builder的顯示名.
*/
public String getDisplayName() {
return "Say hello world";
}
} }
關於構建方法(Perform)的一個實現樣例:
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爲根)
org/sample/hudson/HelloWordBuilder
此外hudson經過固定的命名方式來肯定頁面文件屬於局部配置仍是全局配置:config.jelly提供局部配置;global.jelly提供全局配置
A 局部配置詳解
config.jelly 的內容將被包含在擴展功能的配置中
以HelloWorldBuilder爲例,其擴展的是一個Hudson Job的構建步驟,那麼config.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,而文件的形式大體以下:
<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 app Hudson應用程序對象 如上面的displayName例子 2 it 當前UI所屬的模型對象,在上面的Builder擴展例子中則對應於HelloWorldBuilder對象 ${it.name} 對應於builder的getName()方法 3 h 一個全局的工具類,提供靜態工具方法
經過Job配置界面保存以後,hudson會建立builder對象,並將表單值經過構造器注入,構造器聲明以下:
@DataBoundConstructor public HelloWorldBuilder(String name) { this.name = name; }
而builder必須提供getName方法,這樣可將配置到config.xml中(hudson使用xml存儲配置信息)在從新打開job配置時可自動填值
關於表單值的校驗,以文本域name爲例:
jelly在渲染時自動增長了ajax校驗的功能腳本,因而文本框失去焦點時會往服務端發送校驗請求:
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方法的樣例實現:
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 爲全局配置頁面,示例:
<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方法,能夠實現該方法並提供本身的定製:
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方法 使用場景:在構造器方法中執行load,將全局配置諸如到當前對象中;配置文件經過表達式調用descriptor的get**方法顯示到前端;在保存系統配置時,configure方法中將配置讀入當前對象,並持久化。
3、其餘資料
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的報告發布擴展):
Hudson插件大全介紹 - http://wiki.hudson-ci.org/display/HUDSON/All+Plugins+by+Topic
原文地址:http://blog.csdn.net/littleatp2008/article/details/7001793