1. 程式人生 > >Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)

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

本文首發於:Jenkins 中文社群

huashan

本文介紹了筆者首個 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" 構建步驟,如下圖所示:
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 。 maven-snapshot-check-plugin-usage

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

總結

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

參考