Jenkins 插件開發(fā)

插件開發(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)

jenkins_plugin_project.PNG
  • 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è)源碼插件网棍,例如 GitSvn 插件)薪韩,然后實(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 界面上顯示的名稱谊路。

img

如果我們?cè)诓寮行枰@取一些系統(tǒng)設(shè)置參數(shù)讹躯,我們可以在 Descriptor 中獲取一個(gè)參數(shù)對(duì)應(yīng) Descriptor 中的一個(gè)屬性,其中的 userFrench 屬性是一個(gè)全局配置,可以在系統(tǒng)設(shè)置里面看到這個(gè)屬性潮梯。

img
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ù)。

img

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)容。

jenkins_plugin_helloworld.PNG

用戶自定義參數(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;
    ....
}
img

這個(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>
img
  • 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>
img

在定義一個(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>
img

這是 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 文件,如下圖所示

img

而在需要信息的時(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)是怎樣使用碘赖,參照別人的源碼來寫出自己的插件。

參考

http://www.reibang.com/p/8c05b6191d2f

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末外构,一起剝皮案震驚了整個(gè)濱河市普泡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌审编,老刑警劉巖撼班,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垒酬,居然都是意外死亡砰嘁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門勘究,熙熙樓的掌柜王于貴愁眉苦臉地迎上來般码,“玉大人,你說我怎么就攤上這事乱顾“遄#” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵走净,是天一觀的道長券时。 經(jīng)常有香客問我,道長伏伯,這世上最難降的妖魔是什么橘洞? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮说搅,結(jié)果婚禮上炸枣,老公的妹妹穿的比我還像新娘。我一直安慰自己弄唧,他們只是感情好适肠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著候引,像睡著了一般侯养。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澄干,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天逛揩,我揣著相機(jī)與錄音柠傍,去河邊找鬼。 笑死辩稽,一個(gè)胖子當(dāng)著我的面吹牛惧笛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逞泄,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼徐紧,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了炭懊?” 一聲冷哼從身側(cè)響起并级,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侮腹,沒想到半個(gè)月后嘲碧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡父阻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年愈涩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片加矛。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡履婉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斟览,到底是詐尸還是另有隱情毁腿,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布苛茂,位于F島的核電站已烤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏妓羊。R本人自食惡果不足惜胯究,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躁绸。 院中可真熱鬧裕循,春花似錦、人聲如沸净刮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庭瑰。三九已至星持,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弹灭,已是汗流浹背督暂。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留穷吮,地道東北人逻翁。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像捡鱼,于是被迫代替她去往敵國和親八回。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容