Jenkins 插件開發

插件開發環境搭建

  • 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>

創建一個空的 plugins 工程

  • 默認為空工程
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

插件目錄結構

jenkins_plugin_project.PNG
  • pom.xml:maven 使用這個文件來構建插件,所有的插件都是基于 Plugin Parent Pom
    <parent>
        <groupId>org.jenkins-ci.plugins</groupId>
        <artifactId>plugin</artifactId>
        <version>3.43</version>
        <relativePath />
    </parent>

intellij idea:直接在 ide 中導入 pom 文件就能導入。

調試插件

  • 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 中運行起來了,現在就可以開始進行調試了。

修改端口

mvn hpi:run -Djetty.port =8090

設置上下文路徑

mvn hpi:run -Dhpi.prefix=/jenkins

打包發布

mvn package

該命令會在 target 目錄創建出[插件名稱].hpi 文件,其他用戶可以直接將這個插件上傳安裝到 Jenkins 中使用(或者放到 $JENKINS_HOME/plugins 目錄中)。

Jenkins 插件 Demo HelloWorld

在之前我們使用mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:創建插件目錄時,Jenkins在我們的項目中生成了一個 HelloWorldBuilder 的插件,這是一個官方示例,下面分析一下這個插件的示例源碼

public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
    
}

首先創建一個類繼承于 Builder,代表使用這個插件是一個構建插件(如果繼承于 Scm,代表這個插件是一個源碼插件,例如 GitSvn 插件),然后實現 SimpleBuildStep 接口。

在 Jenkins 的插件中,每一個插件類中都必須要有一個 ==Descriptor 內部靜態類==,它代表一個類的 ’描述者‘,用于指明這是一個擴展點的實現,Jenkins 是通過這個描述者才能知道我們自己寫的插件。

每一個 ‘描述者’ 靜態類都需要被 @Extension注解,Jenkins 內部會掃描 @Extenstion 注解來知道注冊了有哪些插件。

    @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 類中有兩個方法需要我們必須要進行重寫

  • isApplicable
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            return true;
        }

這個方法的返回值代表這個 Builder 在 Project 中是否可用,我們可以將我們的邏輯寫在其中,例如判斷一些參數,最后返回 true 或者 false 來決定這個 Builder 在此處是否可用。

  • getDisplayName
        @Override
        public String getDisplayName() {
            return "Say hello world";
        }

這個方法返回的是一個 String 類型的值,這個名稱會用在 web 界面上顯示的名稱。

img

如果我們在插件中需要獲取一些系統設置參數,我們可以在 Descriptor 中獲取一個參數對應 Descriptor 中的一個屬性,其中的 userFrench 屬性是一個全局配置,可以在系統設置里面看到這個屬性。

img
private boolean useFrench;
public DescriptorImpl() {
    //...
    load();
}

configure() 方法

在 Descirptor 構造函數中使用load()進行加載全局配置,然后我們就可以在插件中獲取到配置信息。

 @Override
 public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
      useFrench = formData.getBoolean("useFrench");
      save();
      return super.configure(req,formData);
  }

當在全局配置修改屬性后,需要在configure()方法中調用save()將全局配置信息持久化到xml,我們可以在workspace的插件名.xml中看到持久化的數據。

img

peform() 方法

在每個插件的 perform() 方法中,是 perform 真正開始執行的地方,我們如果要在插件中完成什么事,代碼邏輯也是寫在 perform 方法中,perform 方法參數中 build 代表當前構建。

  • workspace 代表當前工作目錄,通過 workspace 可以獲取到當前工作目錄的信息,并可以做些操作,如 workspace.copyTo("/home")

  • launcher 代表啟動進程,可以通過 launcher 執行一些命令,如launcher.launch().stdout(listener).cmds("pwd").start();

  • listener 代表一個監聽器,可以將運行的內容信息通過 listener 輸出到前臺 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 界面上的控制臺可以看見如下內容。其中 Jeffrey 為系統設置中輸入的內容。

jenkins_plugin_helloworld.PNG

用戶自定義參數

在 Jenkins 插件中,如果我們需要一些自定義的參數信息,如構建時執行一些命令,命令的內容是由用戶輸入,這個時候需要一個變量來記錄用戶輸入的信息。
所以在 HelloWorkdBuilder 中定義一個屬性與用于輸入的信息相對應,如上面的 name 屬性。

public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
    private final String name;
    ....
}
img

這個屬性的值是在 job 的配置過程中輸入,由 Jenkins 從 web 前端界面傳遞過來的值,我們還需要在HelloWorldBuilder的構造方法中進行參數的注入

public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
    private final String name;
    
    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
}

類似于 Spring 的依賴注入,在這里 Jenkins 要求進行參數注入的構造方法需要用 @DataBoundConstructor 注解標注,以便 Jenkins 可以找到這個構造函數,并且調用這個構造函數,將 web 界面上配置的參數傳遞進HelloWorldBuilder,這樣就可以在 HelloWorldBuilder 中使用這個屬性了。

到此,這個插件的后臺代碼就已經搞定了,現在給大家講講怎么樣編寫這個前端配置的視圖。

Jenkins 中的視圖

Jenkins 使用 Jelly 來編寫視圖,Jelly 是一種基于 Java 技術和 XML 的腳本編制和處理引擎。Jelly 的特點是有許多基于 JSTL (JSP 標準標記庫,JSP Standard Tag Library)、Ant、Velocity 及其它眾多工具的可執行標記。Jelly 還支持 Jexl(Java 表達式語言,Java Expression Language),Jexl 是 JSTL 表達式語言的擴展版本。Jenkins 的界面繪制就是通過 Jelly 實現的。
在 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

在定義一個屬性時,使用<f:entry>標簽代表這是一個屬性,其中title是指在界面上顯示的字段名,而field是指這個屬性在 HelloWorldBuilder 中對應的屬性名,Jenkins 通過這個名稱來與 HelloWorldBuilder 中的屬性相對應,從而使用 @DataBoundConstructor 標注的構造函數將這些變量注入到 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 中的三種視圖,上面也介紹了兩個簡單的控件 textbox 和 checkbox 的使用,更多的關于 Jelly 的視圖使用可以查看jelly官網

Jenkins 數據持久化

之前在web界面上輸入了 name,這個信息在下一次構建的時候仍然存在,說明 Jenkins 中需要使用數據持久化來將我們配置的信息保存下來,而 Jenkins 使用文件來存儲數據(所有數據都存儲在 $JENKINS_HOME),有些數據,比如 console 輸出,會作為文本文件存儲;大多數的結構數據,如一個項目的配置或構建(build)記錄信息則會通過 XStream 持久化為一個 xml 文件,如下圖所示

img

而在需要信息的時候,Jenkins 又從 xml 文件中讀取到相應的數據,返回給應用程序。

總結總結

在本文,主要介紹了 Jenkins 的簡單使用,以及 Jenkins 的插件開發環境,以及 Jenkins 插件結構的一些介紹。本文主要還是做一個簡單入門介紹,如果想要了解更多的關于 Jenkins 的東西,還是需要去看 Jenkins 的官方wiki, 上面有詳細的關于每個擴展點已經 Jenkins 的 api 的使用介紹,同樣,你也可以下載 Jenkins 的源碼來查看內部的一些實現方式。在Github Jenkinci也有很多的關于 Jenkins 插件的源碼,我們可以通過源碼了解一些擴展點是怎樣使用,參照別人的源碼來寫出自己的插件。

參考

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容