Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

Jenkins中文社群發表於2019-05-13

首發於 Jenkins 中文社群

Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

本文介紹了筆者首個 Jenkins 外掛開發的旅程,包括從產生 idea 開始,然後經過外掛定製開發,接著申請將程式碼託管到 jenkinsci GitHub 組織,最後將外掛釋出到 Jenkins 外掛更新中心的過程。

鑑於文章篇幅過長,將分為上下兩篇進行介紹。

從一個 idea 說起

前幾天和朋友聊天時,聊到了 Maven 版本管理領域的 SNAPSHOT 版本依賴問題,這給他帶來了一些困擾,消滅掉歷史遺留應用的 SNAPSHOT 版本依賴並非易事。

類似問題也曾經給筆者帶來過困擾,在最初沒能去規避問題,等到再想去解決問題時卻發現困難重重,牽一髮而動全身,導致這個問題一直被擱置,而這也給筆者留下深刻的印象。

等到再次制定 Maven 規範時,從一開始就考慮強制禁止 SNAPSHOT 版本依賴發到生產環境。

這裡是通過在 Jenkins 構建時做校驗實現的。因為沒有找到提供類似功能的 Jenkins 外掛,目前這個校驗通過 shell 指令碼來實現的,具體的做法是在 Jenkins 任務中 Maven 構建之前增加一個 Execute shell 的步驟,來判斷 pom.xml 中是否包含 SNAPSHOT 關鍵字,如果包含,該次構建狀態將被標記為失敗。指令碼內容如下:

#!/bin/bash
if [[ ` grep -R --include="pom.xml" SNAPSHOT .` =~ "SNAPSHOT" ]]; 
then echo "SNAPSHOT check failed" && grep -R --include="pom.xml" SNAPSHOT . && exit 1; 
else echo "SNAPSHOT check success"; 
fi
複製程式碼

恰好前不久在看 Jenkins 外掛開發文件,那何不通過 Jenkins 外掛的方式實現它呢?

於是筆者開始了首個 Jenkins 外掛開發之旅。

外掛開發過程

Jenkins 是由 Java 語言開發的最流行的 CI/CD 引擎。

說起 Jenkins 強大的開源生態,自然就會說到 Jenkins 外掛。Jenkins 外掛主要用來對 Jenkins 的功能進行擴充套件。目前 Jenkins 社群有上千個外掛,使用者可以根據自己的需求選擇合適的外掛來定製 Jenkins 。

外掛開發準備

外掛開發需要首先安裝 JDK 和 Maven,這裡不做進一步說明。

建立一個外掛

Jenkins 為外掛開發提供了 Maven 原型。開啟一個命令列終端,切換到你想存放 Jenins 外掛原始碼的目錄,執行如下命令:

mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:
複製程式碼

這個命令允許你使用其中一個與 Jenkins 相關的原型生成專案。

$ 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:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 3
Choose io.jenkins.archetypes:hello-world-plugin version:
1: 1.1
2: 1.2
3: 1.3
4: 1.4
Choose a number: 4: 4
......
[INFO] Using property: groupId = unused
Define value for property 'artifactId': maven-snapshot-check
Define value for property 'version' 1.0-SNAPSHOT: :
[INFO] Using property: package = io.jenkins.plugins.sample
Confirm properties configuration:
groupId: unused
artifactId: maven-snapshot-check
version: 1.0-SNAPSHOT
package: io.jenkins.plugins.sample
 Y: : Y
複製程式碼

筆者選擇了 hello-world-plugin 這個原型,並在填寫了一些引數,如artifactId、version 後生成了專案。 可以使用 mvn verify 命令驗證是否可以構建成功。

構建及執行外掛

Maven HPI Plugin 用於構建和打包 Jenkins 外掛。它提供了一種便利的方式來執行一個已經包含了當前外掛的 Jenkins 例項:

mvn hpi:run
複製程式碼

這將安裝一個 Jenkins 例項,可以通過http://localhost:8080/jenkins/ 來訪問。等待控制檯輸出如下內容,然後開啟 Web 瀏覽器並檢視外掛的功能。

INFO: Jenkins is fully up and running
複製程式碼

在 Jenkins 中建立一個自由風格的任務,然後給它取個名字。然後新增 "Say hello world" 構建步驟,如下圖所示:

Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

輸入一個名字,如:Jenkins ,然後儲存該任務,點選構建,檢視構建日誌,輸出如下所示:

Started by user anonymous
Building in workspace /Users/mrjenkins/demo/work/workspace/testjob
Hello, Jenkins! 
Finished: SUCCESS
複製程式碼

定製開發外掛

Jenkins 外掛開發歸功於有一系列擴充套件點。開發人員可以對其進行擴充套件自定義實現一些功能。

這裡有幾個重要的概念需要做下說明:

擴充套件點( ExtensitonPoint )

擴充套件點是 Jenkins 系統某個方面的介面或抽象類。 這些介面定義了需要實現的方法,而 Jenkins 外掛需要實現這些方法。

筆者所寫的外掛需要實現 Builder 這個擴充套件點。 程式碼片段如下:

public class MavenCheck extends Builder {}
複製程式碼

Descriptor 靜態內部類

Descriptor 靜態內部類是一個類的描述者,用於指明這是一個擴充套件點的實現,Jenkins 通過這個描述者才能知道我們寫的外掛。每一個描述者靜態類都需要被 @Extension 註解,Jenkins 內部會掃描 @Extenstion 註解來獲取註冊了哪些外掛。
程式碼片段如下:

@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
    public DescriptorImpl() {
        load();
    }

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

    @Override
    public String getDisplayName() {
        return "Maven SNAPSHOT Check";
    }
}
複製程式碼

在 DesciptorImpl 實現類中有兩個方法需要我們必須要進行重寫: isApplicable() 和 getDisplayName() 。isApplicable() 這個方法的返回值代表這個 Builder 在 Jenkins Project 中是否可用,我們可以將我們的邏輯寫在其中,例如做一些引數校驗,最後返回 true 或 false 來決定這個 Builder 是否可用。

getDisplayName() 這個方法返回的是一個 String 型別的值,這個名稱被用來在 web 介面上顯示。

資料繫結

前端頁面的資料要和後臺服務端進行互動,需要進行資料繫結。 前端 config.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:entry title="check" field="check">
    <f:checkbox />
  </f:entry>
</j:jelly>
複製程式碼

如上所示,需要在 config.jelly 中包含需要傳入的引數配置資訊的選擇框,field 為 check ,這樣可以在 Jenkins 進行配置,然後通過DataBoundConstructor 資料繫結的方式,將引數傳遞到 Java 程式碼中。服務端 Java 程式碼片段如下:

@DataBoundConstructor
public MavenCheck(boolean check) {
    this.check = check;
}
複製程式碼

核心邏輯

筆者所寫的外掛的核心邏輯是檢查 Maven pom.xml 檔案是否包含 SNAPSHOT 版本依賴。

Jenkins 是 Master/Agent 架構, 這就需要讀取 Agent 節點的 workspace 的檔案, 這是筆者在寫外掛時遇到的一個難點。

Jenkins 強大之處在於它的生態,目前有上千個外掛, 筆者參考了 Text-finder Plugin 的原始碼, 並在參考處新增了相關注釋,最終實現了外掛要實現的功能。

詳細程式碼可以檢視 jenkinsci/maven-snapshot-check-plugin 程式碼倉庫。

分發外掛

使用 mvn package 命令可以打包出字尾為 hpi 的二進位制包, 這樣就可以分發外掛,將其安裝到 Jenkins 例項。

外掛使用說明

以下是對外掛的使用簡要描述。

如果勾選了下面截圖中的選擇框, Jenkins 任務在構建時將會檢查 pom.xml 中是否包含 SNAPSHOT 。

Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

如果檢查到的話,則會將該次構建狀態標記為失敗。

Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

總結

文章上篇主要介紹了從產生 idea 到外掛開發完成的過程。 那麼外掛在開發完成後是如何將它託管到 Jenkins 外掛更新中心讓所有使用者都可以看到的呢? 兩天後的文章下篇將對這個過程進行介紹,敬請期待!

參考

作者:王冬輝

Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

相關文章