首發於 Jenkins 中文社群
本文介紹了筆者首個 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 ,然後儲存該任務,點選構建,檢視構建日誌,輸出如下所示:
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 。
如果檢查到的話,則會將該次構建狀態標記為失敗。
總結
文章上篇主要介紹了從產生 idea 到外掛開發完成的過程。 那麼外掛在開發完成後是如何將它託管到 Jenkins 外掛更新中心讓所有使用者都可以看到的呢? 兩天後的文章下篇將對這個過程進行介紹,敬請期待!
參考
作者:王冬輝