Jenkins 插件開(kāi)發(fā)之旅:兩天內(nèi)從 idea 到發(fā)布(上篇)

首發(fā)于 Jenkins 中文社區(qū)

image.png

本文介紹了筆者首個(gè) Jenkins 插件開(kāi)發(fā)的旅程,包括從產(chǎn)生 idea 開(kāi)始,然后經(jīng)過(guò)插件定制開(kāi)發(fā),接著申請(qǐng)將代碼托管到 jenkinsci GitHub 組織,最后將插件發(fā)布到 Jenkins 插件更新中心的過(guò)程。

鑒于文章篇幅過(guò)長(zhǎng),將分為上下兩篇進(jìn)行介紹。

從一個(gè) idea 說(shuō)起

前幾天和朋友聊天時(shí),聊到了 Maven 版本管理領(lǐng)域的 SNAPSHOT 版本依賴問(wèn)題,這給他帶來(lái)了一些困擾,消滅掉歷史遺留應(yīng)用的 SNAPSHOT 版本依賴并非易事。

類似問(wèn)題也曾經(jīng)給筆者帶來(lái)過(guò)困擾,在最初沒(méi)能去規(guī)避問(wèn)題,等到再想去解決問(wèn)題時(shí)卻發(fā)現(xiàn)困難重重,牽一發(fā)而動(dòng)全身,導(dǎo)致這個(gè)問(wèn)題一直被擱置,而這也給筆者留下深刻的印象。

等到再次制定 Maven 規(guī)范時(shí),從一開(kāi)始就考慮強(qiáng)制禁止 SNAPSHOT 版本依賴發(fā)到生產(chǎn)環(huán)境。

這里是通過(guò)在 Jenkins 構(gòu)建時(shí)做校驗(yàn)實(shí)現(xiàn)的。因?yàn)闆](méi)有找到提供類似功能的 Jenkins 插件,目前這個(gè)校驗(yàn)通過(guò) shell 腳本來(lái)實(shí)現(xiàn)的,具體的做法是在 Jenkins 任務(wù)中 Maven 構(gòu)建之前增加一個(gè) Execute shell 的步驟,來(lái)判斷 pom.xml 中是否包含 SNAPSHOT 關(guān)鍵字,如果包含,該次構(gòu)建狀態(tài)將被標(biāo)記為失敗。腳本內(nèi)容如下:

#!/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 插件開(kāi)發(fā)文檔,那何不通過(guò) Jenkins 插件的方式實(shí)現(xiàn)它呢?

于是筆者開(kāi)始了首個(gè) Jenkins 插件開(kāi)發(fā)之旅。

插件開(kāi)發(fā)過(guò)程

Jenkins 是由 Java 語(yǔ)言開(kāi)發(fā)的最流行的 CI/CD 引擎。

說(shuō)起 Jenkins 強(qiáng)大的開(kāi)源生態(tài),自然就會(huì)說(shuō)到 Jenkins 插件。Jenkins 插件主要用來(lái)對(duì) Jenkins 的功能進(jìn)行擴(kuò)展。目前 Jenkins 社區(qū)有上千個(gè)插件,用戶可以根據(jù)自己的需求選擇合適的插件來(lái)定制 Jenkins 。

插件開(kāi)發(fā)準(zhǔn)備

插件開(kāi)發(fā)需要首先安裝 JDK 和 Maven,這里不做進(jìn)一步說(shuō)明。

創(chuàng)建一個(gè)插件

Jenkins 為插件開(kāi)發(fā)提供了 Maven 原型。打開(kāi)一個(gè)命令行終端,切換到你想存放 Jenins 插件源代碼的目錄,運(yùn)行如下命令:

mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:

這個(gè)命令允許你使用其中一個(gè)與 Jenkins 相關(guān)的原型生成項(xiàng)目。

$ 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 這個(gè)原型,并在填寫(xiě)了一些參數(shù),如artifactId、version 后生成了項(xiàng)目。 可以使用 mvn verify 命令驗(yàn)證是否可以構(gòu)建成功。

構(gòu)建及運(yùn)行插件

Maven HPI Plugin 用于構(gòu)建和打包 Jenkins 插件。它提供了一種便利的方式來(lái)運(yùn)行一個(gè)已經(jīng)包含了當(dāng)前插件的 Jenkins 實(shí)例:

mvn hpi:run

這將安裝一個(gè) Jenkins 實(shí)例,可以通過(guò)http://localhost:8080/jenkins/ 來(lái)訪問(wèn)。等待控制臺(tái)輸出如下內(nèi)容,然后打開(kāi) Web 瀏覽器并查看插件的功能。

INFO: Jenkins is fully up and running

在 Jenkins 中創(chuàng)建一個(gè)自由風(fēng)格的任務(wù),然后給它取個(gè)名字。然后添加 "Say hello world" 構(gòu)建步驟,如下圖所示:

image.png

輸入一個(gè)名字,如:Jenkins ,然后保存該任務(wù),點(diǎn)擊構(gòu)建,查看構(gòu)建日志,輸出如下所示:

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

定制開(kāi)發(fā)插件

Jenkins 插件開(kāi)發(fā)歸功于有一系列擴(kuò)展點(diǎn)。開(kāi)發(fā)人員可以對(duì)其進(jìn)行擴(kuò)展自定義實(shí)現(xiàn)一些功能。

這里有幾個(gè)重要的概念需要做下說(shuō)明:

擴(kuò)展點(diǎn)( ExtensitonPoint )

擴(kuò)展點(diǎn)是 Jenkins 系統(tǒng)某個(gè)方面的接口或抽象類。
這些接口定義了需要實(shí)現(xiàn)的方法,而 Jenkins 插件需要實(shí)現(xiàn)這些方法。

筆者所寫(xiě)的插件需要實(shí)現(xiàn) Builder 這個(gè)擴(kuò)展點(diǎn)。
代碼片段如下:

public class MavenCheck extends Builder {}

Descriptor 靜態(tài)內(nèi)部類

Descriptor 靜態(tài)內(nèi)部類是一個(gè)類的描述者,用于指明這是一個(gè)擴(kuò)展點(diǎn)的實(shí)現(xiàn),Jenkins 通過(guò)這個(gè)描述者才能知道我們寫(xiě)的插件。每一個(gè)描述者靜態(tài)類都需要被 @Extension 注解,Jenkins 內(nèi)部會(huì)掃描 @Extenstion 注解來(lái)獲取注冊(cè)了哪些插件。
代碼片段如下:

@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 實(shí)現(xiàn)類中有兩個(gè)方法需要我們必須要進(jìn)行重寫(xiě):
isApplicable() 和 getDisplayName() 。isApplicable() 這個(gè)方法的返回值代表這個(gè) Builder 在 Jenkins Project 中是否可用,我們可以將我們的邏輯寫(xiě)在其中,例如做一些參數(shù)校驗(yàn),最后返回 true 或 false 來(lái)決定這個(gè) Builder 是否可用。

getDisplayName() 這個(gè)方法返回的是一個(gè) String 類型的值,這個(gè)名稱被用來(lái)在 web 界面上顯示。

數(shù)據(jù)綁定

前端頁(yè)面的數(shù)據(jù)要和后臺(tái)服務(wù)端進(jìn)行交互,需要進(jìn)行數(shù)據(jù)綁定。
前端 config.jelly 頁(yè)面代碼片段如下:

<?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 中包含需要傳入的參數(shù)配置信息的選擇框,field 為 check ,這樣可以在 Jenkins 進(jìn)行配置,然后通過(guò)DataBoundConstructor 數(shù)據(jù)綁定的方式,將參數(shù)傳遞到 Java 代碼中。服務(wù)端 Java 代碼片段如下:

@DataBoundConstructor
public MavenCheck(boolean check) {
    this.check = check;
}

核心邏輯

筆者所寫(xiě)的插件的核心邏輯是檢查 Maven pom.xml 文件是否包含 SNAPSHOT 版本依賴。

Jenkins 是 Master/Agent 架構(gòu),
這就需要讀取 Agent 節(jié)點(diǎn)的 workspace 的文件,
這是筆者在寫(xiě)插件時(shí)遇到的一個(gè)難點(diǎn)。

Jenkins 強(qiáng)大之處在于它的生態(tài),目前有上千個(gè)插件,
筆者參考了 Text-finder Plugin 的源碼,
并在參考處添加了相關(guān)注釋,最終實(shí)現(xiàn)了插件要實(shí)現(xiàn)的功能。

詳細(xì)代碼可以查看 jenkinsci/maven-snapshot-check-plugin 代碼倉(cāng)庫(kù)。

分發(fā)插件

使用 mvn package 命令可以打包出后綴為 hpi 的二進(jìn)制包,
這樣就可以分發(fā)插件,將其安裝到 Jenkins 實(shí)例。

插件使用說(shuō)明

以下是對(duì)插件的使用簡(jiǎn)要描述。

如果勾選了下面截圖中的選擇框,
Jenkins 任務(wù)在構(gòu)建時(shí)將會(huì)檢查 pom.xml 中是否包含 SNAPSHOT 。


image.png

如果檢查到的話,則會(huì)將該次構(gòu)建狀態(tài)標(biāo)記為失敗。


image.png

總結(jié)

文章上篇主要介紹了從產(chǎn)生 idea 到插件開(kāi)發(fā)完成的過(guò)程。 那么插件在開(kāi)發(fā)完成后是如何將它托管到 Jenkins 插件更新中心讓所有用戶都可以看到的呢? 兩天后的文章下篇將對(duì)這個(gè)過(guò)程進(jìn)行介紹,敬請(qǐng)期待!

參考

作者:王冬輝

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

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