實(shí)戰(zhàn)Jenkins插件開(kāi)發(fā) - Telegram通知插件

介紹

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)建插件

  1. 在需要?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 
  1. 讓我們確??梢詷?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ě)插件

  1. 創(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)建操作顯示名稱
  1. 全局配置定義
    插件如果需要在全局配置中進(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è)置了。

  1. 項(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)行效果圖如下:


項(xiàng)目配置
  • 插件業(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)建輸出日志如下:


構(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控件

另外

  1. Jenkins插件管理頁(yè)面中,關(guān)于插件介紹的內(nèi)容在index.jelly視圖中進(jìn)行更改
  2. 插件名稱在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)選擇。

典型的工作流如下:

  1. 確定需要本地化的Messages
  2. 將消息寫(xiě)入Messages.properties文件,即可以為每個(gè)package編寫(xiě)一個(gè),也可以整個(gè)module或者plugin只用一個(gè)
  3. 運(yùn)行mvn compile生成Messages.java
  4. 更新代碼,使用最新生成的消息格式方法讀取

代碼中則可以通過(guò)如下的方式進(jìn)行引用

@Override
        public String getDisplayName() {
            return Messages.TelegramPublisher_DescriptorImpl_DisplayName();
        }
2.png

國(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)提示了。


1.png

打包上傳

使用指令mvn package進(jìn)行打包,完成后會(huì)在target目錄下生成hpi文件。

有兩種方式上傳插件到Jenkins

  1. 將hpi文件放到Jenkins下的plugins目錄下
  2. 通過(guò)Jenkins的插件管理中心,進(jìn)行手動(dòng)上傳hpi文件,具體操作步驟如下圖所示:


    插件上傳

本文對(duì)應(yīng)的源代碼,請(qǐng)移步Jenkins Telegram通知插件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容