近期接觸到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插件查找路徑:
<pluginGroups>
<pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
建立項目成功之後,一個helloworld的骨架項目結構如下:
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方法中将配置讀入目前對象,并持久化。
三、其他資料
Hudson的擴充點JavaDoc:
http://wiki.jenkins-ci.org/display/JENKINS/Extension+pointsHudson插件開發簡單介紹:
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.javaHudson插件大全介紹 -
http://wiki.hudson-ci.org/display/HUDSON/All+Plugins+by+Topic原文位址:
http://blog.csdn.net/littleatp2008/article/details/7001793
作者:
zale出處:
http://www.cnblogs.com/littleatp/, 如果喜歡我的文章,請
關注我的公衆号本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出
原文連結如有問題, 可留言咨詢.