介紹
Jenkins的很多功能都是借助它的插件機(jī)制來(lái)實(shí)現(xiàn)的,它本身提供的功能是很少的。開(kāi)發(fā)Jenkins插件是基于擴(kuò)展點(diǎn)(ExtensionPoint)來(lái)完成的,它是Jenkins系統(tǒng)的某個(gè)方面的接口或抽象類,這些接口定義了需要實(shí)現(xiàn)的方法,而Jenkins插件需要實(shí)現(xiàn)這些方法。更多擴(kuò)展點(diǎn)的詳細(xì)信息,可以參考官方ExtensionPoint文檔,通過(guò)這些擴(kuò)展點(diǎn)我們可以寫(xiě)插件來(lái)實(shí)現(xiàn)自己的需求。
下面是一些常用的擴(kuò)展點(diǎn):
- SCM:代表源碼管理的一個(gè)步驟,如Git,CVS,SVN等就是擴(kuò)展的SCM
- Builder:代表構(gòu)建的一個(gè)步驟,我們可以增加一個(gè)構(gòu)建步驟,而每個(gè)選項(xiàng)都是對(duì)應(yīng)一個(gè)Builder,在每一個(gè)Builder中都有自己不同的功能。
- Publisher:代表構(gòu)建后的一個(gè)步驟,比如通知,上傳文件到目標(biāo)服務(wù)器等,我們要開(kāi)發(fā)的Telegram通知插件則是基于此擴(kuò)展點(diǎn)實(shí)現(xiàn)。
- Trigger:代表一個(gè)構(gòu)建的觸發(fā)操作,當(dāng)滿足什么樣的條件才可以觸發(fā)這個(gè)項(xiàng)目的構(gòu)建。比較常用的觸發(fā)就是代碼變更時(shí)自動(dòng)觸發(fā)。
環(huán)境搭建
Jenkins是基于Java語(yǔ)言開(kāi)發(fā)的,因此它的插件也應(yīng)該基于Java來(lái)進(jìn)行開(kāi)發(fā)。它需要安裝Java開(kāi)發(fā)環(huán)境和Maven,至于如何搭建Java和Maven開(kāi)發(fā)環(huán)境,網(wǎng)上有很多教程,這里不做介紹。
安裝完成后,修改maven用戶目錄下的settings.xml文件(如果用戶目錄沒(méi)有settings.xml文件,則從maven安裝目錄下的conf目錄復(fù)制到用戶目錄,maven安裝目錄下的settings.xml是全局配置,針對(duì)所有用戶,不建議更改)
用戶目錄下settings.xml文件位置:
- linux:~/.m2/settings.xml
- windows:%USERPROFILE%.m2\settings.xml
修改為以下內(nèi)容:
<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>
創(chuàng)建插件
- 在需要?jiǎng)?chuàng)建插件的目錄打開(kāi)cmd窗口,運(yùn)行一下命令
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:"
這個(gè)命令將允許你生成與Jenkins相關(guān)的多個(gè)項(xiàng)目原型之一,這里我們使用empty原型1.5版,因此選擇如下:
$ 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.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Choose io.jenkins.archetypes:hello-world-plugin version:
1: 1.0
2: 1.1
3: 1.2
4: 1.3
5: 1.4
6: 1.5
Choose a number: 6: 6
…
[INFO] Using property: groupId = unused
Define value for property 'artifactId': TelegramNotification
Define value for property 'version' 1.0-SNAPSHOT: : 1.0
[INFO] Using property: package = io.jenkins.plugins.sample
Confirm properties configuration:
groupId: unused
artifactId: TelegramNotification
version: 1.0
package: io.jenkins.plugins.sample
Y: : y
- 讓我們確??梢詷?gòu)建插件,執(zhí)行以下命令進(jìn)行驗(yàn)證
mvn verify
驗(yàn)證結(jié)果如下,則說(shuō)明驗(yàn)證通過(guò)。
驗(yàn)證通過(guò)
(如果使用的是其他原型的話,在測(cè)試用例這一步可能會(huì)失敗,則可以考慮在pom.xml中增加以下配置忽略測(cè)試用例)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
編寫(xiě)插件
- 創(chuàng)建一個(gè)TelegramPublisher實(shí)現(xiàn)Notifier類
public class TelegramPublisher extends Notifier {
}
在Jenkins的插件中,每一個(gè)插件類中都必須要有一個(gè)Descriptor內(nèi)部靜態(tài)類,它代表一個(gè)類的描述者,用于指明這個(gè)一個(gè)擴(kuò)展點(diǎn)的實(shí)現(xiàn),Jenkins是通過(guò)這個(gè)描述者才能知道我們自己寫(xiě)的插件。這個(gè)靜態(tài)類需要被@Extension注解,Jenkins內(nèi)部會(huì)掃描@Extension注解來(lái)知道注冊(cè)了哪些插件。
public class TelegramPublisher extends Notifier {
@Symbol("Telegram")
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return false;
}
@Override
public String getDisplayName() {
return "Telegram Notification";
}
}
}
在Descriptor類中有兩個(gè)方法一定需要我們進(jìn)行重寫(xiě)
- isApplicable方法:這個(gè)方法的返回值代表這個(gè)Publisher在項(xiàng)目中是否可用,我們可以將我們的邏輯寫(xiě)在其中,例如判斷一些參數(shù),最后返回true或者false來(lái)決定在項(xiàng)目中是否可用。
-
getDisplayName方法:這個(gè)方法返回的是一個(gè)String類型的值,這個(gè)名稱會(huì)用在構(gòu)建選項(xiàng)下拉框中顯示的名稱。
構(gòu)建操作顯示名稱
- 全局配置定義
插件如果需要在全局配置中進(jìn)行配置的話,我們需要在Descriptor中定義一個(gè)屬性。這里我們需要把Telegram Bot Token和Telegram Bot Name保存到全局配置中,則可以在這里進(jìn)行定義。
- 定義botUserName和botToken(必須與global.jelly中的field相同,global.jelly是Jenkins中的全局配置視圖,在后面進(jìn)行詳細(xì)介紹)
//全局配置中,進(jìn)行Telegram Bot Name設(shè)置,必須與global.jelly中的field字段相同
private String botUserName;
//全局配置中,進(jìn)行Telegram Bot Token設(shè)置,必須與global.jelly中的field字段相同
private String botToken;
public String getBotUserName() {
return botUserName;
}
public String getBotToken() {
return botToken;
}
- 在Descriptor構(gòu)造函數(shù)中使用load()進(jìn)行加載全局配置
public DescriptorImpl() {
load();
}
- 在插件中獲取配置信息并持久化到xml中
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
botUserName = json.getString("botUserName"); //獲取Telegram Bot Name配置
botToken = json.getString("botToken"); //獲取Telegram Bot Token配置
save(); //將全局配置信息持久化到xml中
return super.configure(req, json);
}
- 在global.jelly中定義全局配置視圖,這部分后面講視圖的時(shí)候再來(lái)詳細(xì)講解。
<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="Darren Telegram Bot">
<f:entry title="Telegram Bot name" field="botUserName"
description="Paste your bot name">
<f:textbox />
</f:entry>
<f:entry title="Telegram Bot token" field="botToken"
description="Paste your bot token">
<f:textbox />
</f:entry>
</f:section>
</j:jelly>
注意:這里的field字段必須與Descriptor里面的屬性保持一致,才能綁定上。
使用mvn hpi:run查看運(yùn)行效果

在Manage Jenkins -> Configure System里面,我們已經(jīng)看到了相關(guān)設(shè)置了。
- 項(xiàng)目配置定義
項(xiàng)目的相關(guān)配置,則是定義在插件類本身中,這里就是我們的TelegramPublisher類,其視圖則是定義在config.jelly中,規(guī)則和global.jelly一樣。
- 獲取全局配置信息
我們定義一個(gè)方法來(lái)獲取Descriptor類來(lái)獲取全局配置信息,代碼如下:
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
- 參數(shù)注入
// 項(xiàng)目配置,Telegram消息接收者
private final String receivers;
// 項(xiàng)目配置,額外消息
private final String messageTemplate;
// 項(xiàng)目配置,消息發(fā)送條件,構(gòu)建成功是否發(fā)送消息
private final Boolean condition;
@DataBoundConstructor
public TelegramPublisher(String receivers, String messageTemplate, Boolean condition) {
this.receivers = receivers;
this.messageTemplate = messageTemplate;
this.condition = condition;
}
public String getMessageTemplate() {
return messageTemplate;
}
public String getReceivers() {
return receivers;
}
public Boolean getCondition() {
return condition;
}
這三個(gè)屬性的值是項(xiàng)目配置過(guò)程中輸入,由Jenkins從Web前端界面?zhèn)鬟f過(guò)來(lái),通過(guò)在TelegramPublisher構(gòu)造函數(shù)中進(jìn)行參數(shù)的注入。這個(gè)類似于Spring的依賴注入,需要用@DataBoundConstructor注解標(biāo)注。
- 在config.jelly中定義項(xiàng)目視圖
<?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="Extra Message" field="messageTemplate" description="The extra message">
<f:textbox />
</f:entry>
<f:entry title="Notification Users" field="receivers" description="The users want to receive message,split by #">
<f:textbox />
</f:entry>
<f:entry title="Send if success?" field="condition" description="Sends the notification if the build succeed">
<f:checkbox />
</f:entry>
</j:jelly>
運(yùn)行效果圖如下:

- 插件業(yè)務(wù)邏輯
在每個(gè)插件的perform()方法中,是真正開(kāi)始執(zhí)行的地方,我們?nèi)绻诓寮型瓿墒裁词拢窃趐erform方法中實(shí)現(xiàn),其中的方法參數(shù)build代表當(dāng)前構(gòu)建,launcher代表啟動(dòng)進(jìn)程,listener代表一個(gè)監(jiān)聽(tīng)器,可以將運(yùn)行的內(nèi)容信息通過(guò)listener輸出到前臺(tái)console。
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println("[INFO] test message" + getDescriptor().getBotUserName());
return true;
}
觸發(fā)一次構(gòu)建輸出日志如下:

Jenkins視圖
Jenkins使用jelly來(lái)編寫(xiě)視圖,Jelly是一種基于Java技術(shù)和XML的腳本編制和處理引擎。在Jenkins中的視圖類型有三種
- global.jelly 全局配置視圖
-
config.jelly 項(xiàng)目配置視圖
</f:section>標(biāo)簽用來(lái)標(biāo)記一組屬性集合。<f:entry>標(biāo)簽代表這是一個(gè)屬性,其中title是指界面上顯示的字段名,而field是屬性在插件類中對(duì)應(yīng)的屬性名,description則是描述信息 -
help-屬性名.html 幫助視圖html片段
幫助視圖
這就是Jenkins中的三種視圖,更多關(guān)于Jelly的視圖控件可以查看Jenkins控件
另外:
- Jenkins插件管理頁(yè)面中,關(guān)于插件介紹的內(nèi)容在index.jelly視圖中進(jìn)行更改
- 插件名稱在pom.xml中更改name元素即可


配置文件與多語(yǔ)言國(guó)際化
-
config.jelly視圖和global.jelly視圖
在resources中創(chuàng)建對(duì)應(yīng)的properties文件,比如config.properties對(duì)應(yīng)的config.jelly視圖的配置文件(這個(gè)是默認(rèn)英文版本的),其他語(yǔ)言版本,使用config_語(yǔ)言編碼.properties文件,比如簡(jiǎn)體中文,則使用config_zh_CN.properties配置文件,global.jelly視圖類似。
格式使用屬性名=數(shù)據(jù)的方式,如下圖所示:
配置文件格式
使用的時(shí)候,則遵照${%屬性名}的格式
配置使用 help幫助視圖
help幫助視圖多語(yǔ)言,采用的是help-屬性名_語(yǔ)言編碼.html的方式-
插件類多語(yǔ)言
創(chuàng)建Messages.properties配置的方式來(lái)實(shí)現(xiàn),其他語(yǔ)言如上面兩種情況一樣,使用Messages_語(yǔ)言編碼.properties的方式實(shí)現(xiàn)。Jenkins使用Localizer生成Messages類,能夠以類型安全的方式訪問(wèn)Message資源。src/main/resources/**/Messages.properties匹配的所以文件都會(huì)生成一個(gè)對(duì)應(yīng)的Messages類。如過(guò)IDE找不到這些類,需要手動(dòng)將target/generated-sources/localizer目錄加入源碼的根目錄。返回用于顯示的字符串的代碼(如Descriptor.getDisplayName())可以使用Messages類獲取本地化的消息。在運(yùn)行時(shí),適當(dāng)?shù)膌ocale會(huì)被自動(dòng)選擇。
典型的工作流如下:
- 確定需要本地化的Messages
- 將消息寫(xiě)入Messages.properties文件,即可以為每個(gè)package編寫(xiě)一個(gè),也可以整個(gè)module或者plugin只用一個(gè)
- 運(yùn)行mvn compile生成Messages.java
- 更新代碼,使用最新生成的消息格式方法讀取
代碼中則可以通過(guò)如下的方式進(jìn)行引用
@Override
public String getDisplayName() {
return Messages.TelegramPublisher_DescriptorImpl_DisplayName();
}

國(guó)際化更多內(nèi)容,請(qǐng)查看Jenkins官方文檔
參數(shù)檢查
Jenkins中進(jìn)行字段檢驗(yàn),是通過(guò)創(chuàng)建doCheck字段名稱()這樣的方法來(lái)處理的,value參數(shù),則代表該字段的值。
///校驗(yàn)botUserName參數(shù)
public FormValidation doCheckBotUserName(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error(Messages.TelegramPublisher_DescriptorImpl_errors_missingBotUserName());
return FormValidation.ok();
}
///校驗(yàn)botToken參數(shù)
public FormValidation doCheckBotToken(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error(Messages.TelegramPublisher_DescriptorImpl_errors_missingBotToken());
return FormValidation.ok();
}
這樣的話,當(dāng)我們?cè)谌峙渲弥?,未填?xiě)botUserName和botToken就會(huì)收到相應(yīng)提示了。

打包上傳
使用指令mvn package進(jìn)行打包,完成后會(huì)在target目錄下生成hpi文件。
有兩種方式上傳插件到Jenkins
- 將hpi文件放到Jenkins下的plugins目錄下
-
通過(guò)Jenkins的插件管理中心,進(jìn)行手動(dòng)上傳hpi文件,具體操作步驟如下圖所示:
插件上傳
本文對(duì)應(yīng)的源代碼,請(qǐng)移步Jenkins Telegram通知插件





