jenkins本身提供了一套插件的管理機制,這些插件允許可插撥形式存在。jenkins插件雖然能提供很多種插件,但還是不能滿足我們持續集成的需要,所以需要定製一些插件來支撐整個持續集成平臺的運行。
Jenkins運行週期:
1.checkout -check out出源碼
2.Pre-build - 預編譯
3.Build wrapper-準備構建的環境,設置環境變量等
4.Builder runs -執行構建,比如調用calling Ant, Make 等等
5.Recording - 記錄輸出,如測試結果
6.Notification - 通知成員
Jenkins提供的擴展點:
爲了支撐插件能在各個生命週期中運行,jenkins提供了各種擴展點,我們主類必須要extends一個擴展點;針對現狀,基本上只需要使用Notifier,與builder這兩個擴展點;詳細如下:
1.Builder:這個擴展點支撐的是構建這個階段需要做的事情,包括prebuild postbuid、構建環境等步驟,例如我們更換slave機器的hosts插件
2.Notifier:包括通知、消息通知等情況,我們的sendnapolimessage是基於這種擴展點來開發的
具體擴展點說明請參考:Extension+points
插件開始手冊:Plugin+tutorial
下面說一下開發流程:
開發環境需要安裝maven和jdk環境。
1.maven配置
配置maven的settings.xml配置文件
<settings>
<pluginGroups>
<pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
<mirrors>
<mirror>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
<mirrorOf>m.g.o-public</mirrorOf>
</mirror>
</mirrors>
<profiles>
<!-- Give access to Jenkins plugins -->
<profile>
<id>jenkins</id>
<activation>
<activeByDefault>true</activeByDefault> <!-- change this to false, if you don't like to have it on per default -->
</activation>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
2.創建插件maven項目
運行命令:mvn -cpu hpi:create
該操作需要你輸入一些參數,比如說groupid=cn.slimsmart.jenkins.plugin,artifactid=helloword。之後會創建一個新的插件模板便於開發者之後的開發工作。
創建完成使用mvn clean package試試是否可以打包完成。
注:我遇見javadoc: 錯誤 - java.lang.IllegalArgumentException錯誤,原因是在解析環境變量classpath時使用了%JAVA_HOME%導致,修改classpath不適用變量就解決了。
創建的項目結構如下:
pom.xml:Maven的構建配置文件
src/main/java:Java源文件目錄
src/main/resources:插件Jelly/Grovy視圖
src/main/webapps:插件的靜態資源如images和html文件
生成的代碼中包含一個HelloWorldBuilder模板。當用戶配置項目並啓用此構建器時{@link DescriptorImpl#newInstance(StaplerRequest)}會被調用創建一個{@link HelloWorldBuilder} 實例創建實例通過使用持久化到項目配置XML XStream,所以這樣可以使用實例字段(如{@link #name})記住配置。執行構建時,{@link #perform}方法將被調用。
3.調試運行
增加遠程調試
#window
set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
#linux
#export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
mvn hpi:run -Djetty.port=8090 -Dhpi.prefix=/jenkins
#hpi.prefix設置context path
可以在一運行的jenkins的web界面中由Manage Jenkins>Manage Plugins>Advanced上傳插件。
設置及構建運行
4.實例代碼
package cn.slimsmart.jenkins.plugin.helloword;
import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.util.FormValidation;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;
import javax.servlet.ServletException;
import java.io.IOException;
/**
* <p>
* 當用戶配置項目並啓用此構建器時{@link DescriptorImpl#newInstance(StaplerRequest)}會被調用創建一個{@link HelloWorldBuilder} 實例
* 創建實例通過使用持久化到項目配置XML XStream,所以這樣可以使用實例字段(如{@link #name})記住配置。
* 執行構建時,{@link #perform}方法將被調用。
* </p>
*/
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
private final String name;
// config.jelly中的textbox字段必須與“DataBoundConstructor”中的參數名匹配
@DataBoundConstructor
public HelloWorldBuilder(String name) {
this.name = name;
}
/**
* 我們將在{@code config.jelly}中使用它。
*/
public String getName() {
return name;
}
/**
* 執行構建時,perform方法將被調用
* Build參數是描述了當前任務的一次構建,通過它可以訪問到一些比較重要的模型對象如:project當前項目的對象、workspace構建的工作空間、Result當前構建步驟的結果。
* Launcher參數用於啓動構建。
* BuildListener該接口用於檢查構建過程的狀態(開始、失敗、成功..),通過它可以在構建過程中發送一些控制檯信息給jenkins。
* perform方法的返回值告訴jenkins當前步驟是否成功,如果失敗了jenkins將放棄後續的步驟。
*/
@Override
public void perform(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) {
// This is where you 'build' the project.
// Since this is a dummy, we just say 'hello world' and call that a build.
// This also shows how you can consult the global configuration of the builder
if (getDescriptor().getUseFrench())
listener.getLogger().println("Bonjour, "+name+"!");
else{
listener.getLogger().println("Hello, "+name+"!");
}
listener.getLogger().println("workspace="+workspace);
listener.getLogger().println("number="+build.getNumber());
listener.getLogger().println("url="+build.getUrl());
}
// Overridden for better type safety.
// If your plugin doesn't really define any property on Descriptor,
// you don't have to do this.
//插件描述類
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}
/**
* Descriptor for {@link HelloWorldBuilder}. Used as a singleton.
* The class is marked as public so that it can be accessed from views.
*
* <p>
* See {@code src/main/resources/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly}
* for the actual HTML fragment for the configuration screen.
* 用於配置屏幕的實際HTML片段
*/
@Extension // This indicates to Jenkins that this is an implementation of an extension point.
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
/**
* 要保留全局配置信息,只需將其存儲在一個字段中並調用save
* 如果您不希望字段持久化,請使用{@code transient}。
* global.jelly中checkbox的useFrench一至,全局配置
*/
private boolean useFrench;
/**
* In order爲了加載持久化的全局配置,你必須在構造函數中調用load()
*/
public DescriptorImpl() {
load();
}
/**
* Performs on-the-fly validation of the form field 'name'.
* 執行表單字段“名稱”的即時驗證。
* @param value
* This parameter receives the value that the user has typed.
* @return
* Indicates the outcome of the validation. This is sent to the browser.
* <p>
* Note that returning {@link FormValidation#error(String)} does not
* prevent the form from being saved. It just means that a message
* will be displayed to the user.
*/
public FormValidation doCheckName(@QueryParameter String value)
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();
}
// 表示此構建器是否可用於各種項目類型
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
// Indicates that this builder can be used with all kinds of project types
return true;
}
/**
* This human readable name is used in the configuration screen.
*/
public String getDisplayName() {
return "Say hello world";
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
// To persist global configuration information,
// set that to properties and call save().
useFrench = formData.getBoolean("useFrench");
// ^Can also use req.bindJSON(this, formData);
// (easier when there are many fields; need set* methods for this, like setUseFrench)
save();
return super.configure(req,formData);
}
/**
* This method returns true if the global configuration says we should speak French.
*
* The method name is bit awkward because global.jelly calls this method to determine
* the initial state of the checkbox by the naming convention.
*/
public boolean getUseFrench() {
return useFrench;
}
}
}
該實例使用了jenkins的Builder作爲擴展點,實現了BuildStep,通過內部類DescripotorImpl添加@Extension聲明,告訴系統該內部類是作爲BuildStepDescriptor的擴展出現。
這裏基本完成了擴展點的後臺代碼部分,但是擴展過程中還需要對前端頁面進行擴張,這時就需要建立一個pcakage放置該擴展類對應的視圖。Jenkins使用了Jelly頁面渲染技術,這是一個基於XML的服務端頁面渲染引擎,其將基於Jelly的xml標籤轉換爲對應的Html標籤並輸出到客戶端。模型對象的信息通過Jexl表達式被傳遞到頁面上(相當於Jsp的JSTL)。jelly文件以.jelly爲後綴,在hudson中使用類全名的形式來查找模型類對應的jelly頁面文件,如名爲src/main/java/cn/slimsmart/jenkins/plugin/helloword/HelloWorldBuilder.java的類,其對應的頁面文件應該存在於src/main/resources/cn/slimsmart/jenkins/plugin/helloword/HelloWorldBuilder目錄的下。
視圖有三種:
1.全局配置(global.jelly->系統管理-系統設置)
2.Job配置(config.jelly->每個Job而言需要的配置信息)
3.還有就是使用幫助(help-字段名).html
關於jelly學習參考:Basic+guide+to+Jelly+usage+in+Jenkins
參考文章:
1.jenkins插件開發
2.淺析 Jenkins 插件開發
3.Jenkins插件開發入門