插件開發(fā)環(huán)境搭建
- maven 3
- jdk 6 +
修改 maven 配置文件 settings.xml
<settings>
<pluginGroups>
<pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
<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>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<mirrors>
<mirror>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
<mirrorOf>m.g.o-public</mirrorOf>
</mirror>
</mirrors>
</settings>
創(chuàng)建一個(gè)空的 plugins 工程
- 默認(rèn)為空工程
mvn archetype:generate -Dfilter=io.jenkins.archetypes:empty-plugin
-
hello_world
工程
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:
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:global-shared-library (Uses the Jenkins Pipeline Unit mock library to test the usage of a Global Shared Library)
4: remote -> io.jenkins.archetypes:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.)
5: remote -> io.jenkins.archetypes:scripted-pipeline (Uses the Jenkins Pipeline Unit mock library to test the logic inside a Pipeline script.)
# 4 HelloWorld
插件目錄結(jié)構(gòu)
-
pom.xml
:maven 使用這個(gè)文件來構(gòu)建插件闽颇,所有的插件都是基于 Plugin Parent Pom
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>3.43</version>
<relativePath />
</parent>
intellij idea:直接在 ide 中導(dǎo)入 pom 文件就能導(dǎo)入讹开。
調(diào)試插件
- linux
export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
mvn hpi:run
- windows
set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
mvn hpi:run
輸入命令過后可以打開瀏覽器进每,輸入:http://localhost:8080/jenkins, 就可以看見你的插件在 jenkins 中運(yùn)行起來了弟翘,現(xiàn)在就可以開始進(jìn)行調(diào)試了。
修改端口
mvn hpi:run -Djetty.port =8090
設(shè)置上下文路徑
mvn hpi:run -Dhpi.prefix=/jenkins
打包發(fā)布
mvn package
該命令會(huì)在 target 目錄創(chuàng)建出[插件名稱].hpi
文件莽红,其他用戶可以直接將這個(gè)插件上傳安裝到 Jenkins 中使用(或者放到 $JENKINS_HOME/plugins
目錄中)妥畏。
Jenkins 插件 Demo HelloWorld
在之前我們使用
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:
創(chuàng)建插件目錄時(shí),Jenkins在我們的項(xiàng)目中生成了一個(gè) HelloWorldBuilder 的插件安吁,這是一個(gè)官方示例醉蚁,下面分析一下這個(gè)插件的示例源碼
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
}
首先創(chuàng)建一個(gè)類繼承于 Builder
,代表使用這個(gè)插件是一個(gè)構(gòu)建插件(如果繼承于 Scm
鬼店,代表這個(gè)插件是一個(gè)源碼插件网棍,例如 Git
,Svn
插件)薪韩,然后實(shí)現(xiàn) SimpleBuildStep
接口确沸。
在 Jenkins 的插件中捌锭,每一個(gè)插件類中都必須要有一個(gè) ==Descriptor
內(nèi)部靜態(tài)類==俘陷,它代表一個(gè)類的 ’描述者‘,用于指明這是一個(gè)擴(kuò)展點(diǎn)的實(shí)現(xiàn)观谦,Jenkins 是通過這個(gè)描述者才能知道我們自己寫的插件拉盾。
每一個(gè) ‘描述者’ 靜態(tài)類都需要被 @Extension
注解,Jenkins 內(nèi)部會(huì)掃描 @Extenstion
注解來知道注冊(cè)了有哪些插件豁状。
@Symbol("greet")
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
public FormValidation doCheckName(@QueryParameter String value, @QueryParameter boolean useFrench)
throws IOException, ServletException {
if (value.length() == 0)
return FormValidation.error("error");
if (value.length() < 4)
return FormValidation.warning("warning");
if (!useFrench && value.matches(".*[éáà?].*")) {
return FormValidation.warning("warning");
}
return FormValidation.ok();
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
@Override
public String getDisplayName() {
return "Say hello world";
}
}
在 Desciptor
類中有兩個(gè)方法需要我們必須要進(jìn)行重寫
isApplicable
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
這個(gè)方法的返回值代表這個(gè) Builder
在 Project 中是否可用捉偏,我們可以將我們的邏輯寫在其中,例如判斷一些參數(shù)泻红,最后返回 true
或者 false
來決定這個(gè) Builder
在此處是否可用夭禽。
getDisplayName
@Override
public String getDisplayName() {
return "Say hello world";
}
這個(gè)方法返回的是一個(gè) String 類型的值,這個(gè)名稱會(huì)用在 web 界面上顯示的名稱谊路。
如果我們?cè)诓寮行枰@取一些系統(tǒng)設(shè)置參數(shù)讹躯,我們可以在 Descriptor
中獲取一個(gè)參數(shù)對(duì)應(yīng) Descriptor
中的一個(gè)屬性,其中的 userFrench
屬性是一個(gè)全局配置,可以在系統(tǒng)設(shè)置里面看到這個(gè)屬性潮梯。
private boolean useFrench;
public DescriptorImpl() {
//...
load();
}
configure() 方法
在 Descirptor 構(gòu)造函數(shù)中使用load()
進(jìn)行加載全局配置骗灶,然后我們就可以在插件中獲取到配置信息。
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
useFrench = formData.getBoolean("useFrench");
save();
return super.configure(req,formData);
}
當(dāng)在全局配置修改屬性后秉馏,需要在configure()
方法中調(diào)用save()
將全局配置信息持久化到xml耙旦,我們可以在workspace的插件名.xml中看到持久化的數(shù)據(jù)。
peform() 方法
在每個(gè)插件的 perform()
方法中萝究,是 perform
真正開始執(zhí)行的地方免都,我們?nèi)绻诓寮型瓿墒裁词拢a邏輯也是寫在 perform
方法中帆竹,perform
方法參數(shù)中 build
代表當(dāng)前構(gòu)建琴昆。
workspace
代表當(dāng)前工作目錄,通過workspace
可以獲取到當(dāng)前工作目錄的信息馆揉,并可以做些操作业舍,如workspace.copyTo("/home")
。launcher
代表啟動(dòng)進(jìn)程升酣,可以通過launcher
執(zhí)行一些命令舷暮,如launcher.launch().stdout(listener).cmds("pwd").start();
。listener
代表一個(gè)監(jiān)聽器噩茄,可以將運(yùn)行的內(nèi)容信息通過listener
輸出到前臺(tái)console output
下面。
@Override
public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
if (useFrench) {
listener.getLogger().println("Bonjour, " + name + "!");
} else {
listener.getLogger().println("Hello, " + name + "!");
}
}
如上面的代碼所示,在 perform
方法中我們通過 listener
打印了一行信息绩聘。在 web 界面上的控制臺(tái)可以看見如下內(nèi)容沥割。其中 Jeffrey
為系統(tǒng)設(shè)置中輸入的內(nèi)容。
用戶自定義參數(shù)
在 Jenkins 插件中凿菩,如果我們需要一些自定義的參數(shù)信息机杜,如構(gòu)建時(shí)執(zhí)行一些命令,命令的內(nèi)容是由用戶輸入衅谷,這個(gè)時(shí)候需要一個(gè)變量來記錄用戶輸入的信息椒拗。
所以在 HelloWorkdBuilder 中定義一個(gè)屬性與用于輸入的信息相對(duì)應(yīng),如上面的 name
屬性获黔。
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
private final String name;
....
}
這個(gè)屬性的值是在 job 的配置過程中輸入蚀苛,由 Jenkins 從 web 前端界面?zhèn)鬟f過來的值,我們還需要在HelloWorldBuilder的構(gòu)造方法中進(jìn)行參數(shù)的注入
public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
private final String name;
@DataBoundConstructor
public HelloWorldBuilder(String name) {
this.name = name;
}
類似于 Spring 的依賴注入玷氏,在這里 Jenkins 要求進(jìn)行參數(shù)注入的構(gòu)造方法需要用 @DataBoundConstructor
注解標(biāo)注堵未,以便 Jenkins 可以找到這個(gè)構(gòu)造函數(shù),并且調(diào)用這個(gè)構(gòu)造函數(shù)盏触,將 web 界面上配置的參數(shù)傳遞進(jìn)HelloWorldBuilder渗蟹,這樣就可以在 HelloWorldBuilder 中使用這個(gè)屬性了侦厚。
到此,這個(gè)插件的后臺(tái)代碼就已經(jīng)搞定了拙徽,現(xiàn)在給大家講講怎么樣編寫這個(gè)前端配置的視圖刨沦。
Jenkins 中的視圖
Jenkins 使用 Jelly 來編寫視圖,Jelly 是一種基于 Java
技術(shù)和 XML
的腳本編制和處理引擎膘怕。Jelly 的特點(diǎn)是有許多基于 JSTL (JSP 標(biāo)準(zhǔn)標(biāo)記庫想诅,JSP Standard Tag Library)、Ant岛心、Velocity 及其它眾多工具的可執(zhí)行標(biāo)記来破。Jelly 還支持 Jexl(Java 表達(dá)式語言,Java Expression Language)忘古,Jexl 是 JSTL 表達(dá)式語言的擴(kuò)展版本徘禁。Jenkins 的界面繪制就是通過 Jelly 實(shí)現(xiàn)的。
在 Jenkins 中的視圖的類型有三種:
- global.jelly:全局的配置視圖
<?jelly escape-by-default='true'?>
<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:section title="Hello World Builder">
<f:entry title="French" field="useFrench"
description="Check if we should say hello in French">
<f:checkbox />
</f:entry>
</f:section>
</j:jelly>
- config.jelly:Job的配置視圖
<?jelly escape-by-default='true'?>
<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="Name" field="name">
<f:textbox />
</f:entry>
</j:jelly>
在定義一個(gè)屬性時(shí)髓堪,使用<f:entry>
標(biāo)簽代表這是一個(gè)屬性送朱,其中title
是指在界面上顯示的字段名,而field
是指這個(gè)屬性在 HelloWorldBuilder 中對(duì)應(yīng)的屬性名干旁,Jenkins 通過這個(gè)名稱來與 HelloWorldBuilder 中的屬性相對(duì)應(yīng)驶沼,從而使用 @DataBoundConstructor
標(biāo)注的構(gòu)造函數(shù)將這些變量注入到 HelloWorldBuilder 類中。
- help-屬性名.html:幫助視圖 html 片段
<div>
Help file for fields are discovered through a file name convention. This file is
help for the "name" field. You can have <i>arbitrary</i> HTML here. You can write
this file as a Jelly script if you need a dynamic content (but if you do so, change
the extension to <tt>.jelly</tt>).
</div>
這是 Jenkins 中的三種視圖争群,上面也介紹了兩個(gè)簡單的控件 textbox 和 checkbox 的使用回怜,更多的關(guān)于 Jelly 的視圖使用可以查看jelly官網(wǎng)。
Jenkins 數(shù)據(jù)持久化
之前在web界面上輸入了 name
换薄,這個(gè)信息在下一次構(gòu)建的時(shí)候仍然存在玉雾,說明 Jenkins 中需要使用數(shù)據(jù)持久化來將我們配置的信息保存下來,而 Jenkins 使用文件來存儲(chǔ)數(shù)據(jù)(所有數(shù)據(jù)都存儲(chǔ)在 $JENKINS_HOME
)轻要,有些數(shù)據(jù)复旬,比如 console 輸出,會(huì)作為文本文件存儲(chǔ)伦腐;大多數(shù)的結(jié)構(gòu)數(shù)據(jù)赢底,如一個(gè)項(xiàng)目的配置或構(gòu)建(build)記錄信息則會(huì)通過 XStream 持久化為一個(gè) xml
文件,如下圖所示
而在需要信息的時(shí)候失都,Jenkins 又從 xml
文件中讀取到相應(yīng)的數(shù)據(jù)柏蘑,返回給應(yīng)用程序。
總結(jié)總結(jié)
在本文粹庞,主要介紹了 Jenkins 的簡單使用咳焚,以及 Jenkins 的插件開發(fā)環(huán)境,以及 Jenkins 插件結(jié)構(gòu)的一些介紹庞溜。本文主要還是做一個(gè)簡單入門介紹革半,如果想要了解更多的關(guān)于 Jenkins 的東西碑定,還是需要去看 Jenkins 的官方wiki, 上面有詳細(xì)的關(guān)于每個(gè)擴(kuò)展點(diǎn)已經(jīng) Jenkins 的 api 的使用介紹又官,同樣延刘,你也可以下載 Jenkins 的源碼來查看內(nèi)部的一些實(shí)現(xiàn)方式。在Github Jenkinci也有很多的關(guān)于 Jenkins 插件的源碼六敬,我們可以通過源碼了解一些擴(kuò)展點(diǎn)是怎樣使用碘赖,參照別人的源碼來寫出自己的插件。
參考