Jenkins插件開發完全示例


Jenkins插件開發完全示例

插件功能

在Jenkins構建之前選擇Slave Server進行構建。
Slave Server搭建

准備工作

安裝Java

安裝Maven

命名規約

artifactId:

  • 使用小寫 ID ,並根據需要使用連字符分隔術語
  • 除非名稱有任何意義,否則不要在 ID 中包含 jenkins 或 plugin
  • 本示例的artifactId是:slave-server-parameter

插件名稱:

  • 插件的名稱在 Jenkins UI 和其它地方(如:插件站點)展示給用戶
  • 建議使用簡短的描述性名稱,如 Subversion
  • 本示例的插件名稱叫:Slave Server Parameter Plug-In

groupId:

  • 推薦使用 io.jenkins.plugins 或 org.jenkins-ci.plugins 作為 groupId
  • 但是不禁止其他組織 ID ,除非它們是惡意的
  • 本示例的GroupId是:io.jenkins.plugins

Java 源代碼:

  • 一般遵循Oracle Java 代碼規約
  • 本示例的IDE使用IntelliJ IDEA (Community Edition),並安裝了Alibaba Java Code Guidelines插件規范代碼規約

創建項目

# 在項目文件夾下執行
mvn -U archetype:generate -Dfilter=io.jenkins.archetypes:

PS:在Generating project in Interactive mode會等一會兒。

選擇創建一個空項目

Choose archetype:
1: remote -> io.jenkins.archetypes:empty-plugin (-)
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:empty-plugin version:
1: 1.0
2: 1.1
3: 1.2
4: 1.3
5: 1.4
6: 1.5
7: 1.6
Choose a number: 7: 7
Downloading from repo.bluersw.com: http://repo.bluersw.com:8081/repository/maven-public/io/jenkins/archetypes/empty-plugin/1.6/empty-plugin-1.6.pom
Downloaded from repo.bluersw.com: http://repo.bluersw.com:8081/repository/maven-public/io/jenkins/archetypes/empty-plugin/1.6/empty-plugin-1.6.pom (717 B at 991 B/s)
Downloading from repo.bluersw.com: http://repo.bluersw.com:8081/repository/maven-public/io/jenkins/archetypes/empty-plugin/1.6/empty-plugin-1.6.jar
Downloaded from repo.bluersw.com: http://repo.bluersw.com:8081/repository/maven-public/io/jenkins/archetypes/empty-plugin/1.6/empty-plugin-1.6.jar (1.5 kB at 3.7 kB/s)
[INFO] Using property: groupId = unused
Define value for property 'artifactId': slave-server-parameter
Define value for property 'version' 1.0-SNAPSHOT: :
[INFO] Using property: package = unused
Confirm properties configuration:
groupId: unused
artifactId: slave-server-parameter
version: 1.0-SNAPSHOT
package: unused
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: empty-plugin:1.6
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: slave-server-parameter
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: unused
[INFO] Parameter: packageInPathFormat, Value: unused
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: unused
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: slave-server-parameter
[INFO] Project created from Archetype in dir: /Users/sunweisheng/Documents/Test-Jenkins-Plugin/slave-server-parameter
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:05 min
[INFO] Finished at: 2020-06-13T08:42:26+08:00
[INFO] ------------------------------------------------------------------------
sunweisheng@localhost Test-Jenkins-Plugin %

PS:將項目目錄下的文件拷貝到GitHub倉庫目錄下面,並用IDEA打開項目。

Alt text

Debug

export MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"

mvn hpi:run -Djetty.port=8090

Alt text

在IDEA右上方Edit Configurations...
Alt text

創建Remote-Debug

Alt text
Alt text

應用啟動后,在右上方點擊Debug ‘Remote-Debug’
Alt text

訪問http://localhost:8090/jenkins/ 進入Debug模式。

IntelliJ IDEA設置

因為插件的國際化使用Resource Bundle實現,中文在.properties文件中需要用Unicode的編碼存儲,所以需要設置IDE自動對properties文件內容的中文用native2ascii工具進行轉碼。在IDEA設置界面查找File Encodings,勾選Transparent native-to-ascii conversion。
Alt text

因為Jenkins中插件對象創建后串行化到文件中,使用的時候再並行化到內存,所以類的serialVersionUID屬性就很重要了,這個屬性可以設置IDEA自動生成,在IDEA的設置界面搜索Inspections,在右側再搜索Serialization issues,勾選所有選項,在類名上Alt+Enter就可以看見“Add 'serialVersionUID' field”選項了。
Alt text

創建JavaDOC注釋的快捷鍵,在IDEA的設置中點擊Keymap,在右側搜索Fix doc comment,雙擊搜索結果設置快捷鍵。
Alt text

構建參數定義類

/**
 * @author sunweisheng
 * Jenkins 構建參數:Slave服務器選擇(Jenkins build parameters: Slave server selection)
 */
public class SlaveParameterDefinition extends ParameterDefinition implements  Comparable<SlaveParameterDefinition>

該類繼承了ParameterDefinition代表這是一個構建參數,ParameterDefinition是Jenkins提供擴展點之一(Jenkins擴展點官方資料),實現Comparable接口是因為一個構建項目可能包含多個我們編寫的Slave服務器參數。

該類里面主要方法:

/**
* 構造函數在構建項目配置構建參數時調用(The constructor is called when the build project configures build parameter)
* @param name 構建參數名稱 (Build parameter name)
* @param defaultValue 該構建參數的默認值,會隨着每次用戶選擇Slave服務器而改變(The default value of this build parameter will change each time the user selects the slave server)
*/
@DataBoundConstructor
public SlaveParameterDefinition(String name,String defaultValue) {
	super(name, DESCRIPTION);
	this.uuid = UUID.randomUUID();
	this.setDefaultValue(defaultValue);
}

構建的函數的參數:參數名稱和默認值,如果定義name是slave-name以后在構建腳本里就可以使用params['slave-name']讀取該參數的內容,創建該對象后Jenkins就會將該對象串行化到硬盤上進行存儲,使用時再並行化到內存中使用,所以該對象內的數據都會被保存起來。

對應的配置頁面在項目目錄/src/main/resources/com/bluersw/SlaveParameterDefinition/config.jelly,這里的文件路徑和SlaveParameterDefinition類的文件路徑要保持一致,SlaveParameterDefinition的文件路徑是:
項目目錄/src/main/java/com/bluersw/SlaveParameterDefinition.java。

<?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="${%name}" field="name">
		<f:textbox />
	</f:entry>
	<f:advanced>
		<f:entry title="${%defaultValue}" field="defaultValue" description="${%defaultValueDescr}">
			<f:textbox />
		</f:entry>
	</f:advanced>
</j:jelly>

其中title和description綁定的數據是從同目錄下的*.properties文件中讀取的,field是提交時在類中定義的參數值,語言會根據系統語言自動選擇。

Name=Name
defaultValue=默認的Slave服務器名稱
defaultValueDescr=請輸入一個Slave服務器名稱。

展現效果如下:
Alt text

點擊保存的時候會調用驗證name是否合法的方法,這個方法是在SlaveParameterDefinition類中定義一個名叫DescriptorImpl的內部靜態類:

/**
* 參數描述類,實現了與UI交互的方法。(The parameter description class implements the method of interacting with the UI.)
*/
@Symbol("slaveParameter")
@Extension
public static class DescriptorImpl extends ParameterDescriptor

該類繼承ParameterDescriptor擴展點,基本上和UI交互的方法都定義在這個類中了,就連實例化SlaveParameterDefinition對象的方法也是定義這個類中,可以說Jenkins主要通過ParameterDescriptor類操作各種構建參數對象。

驗證方法如下:

/**
* 驗證用戶輸入的參數名稱是否合法,注意一定是“doCheck”+“要檢查的參數名稱”形式為方法名稱。(Verify that the parameter name entered by the user is legal. Note that it must be in the form of "doCheck" + "parameter name to be checked" as the method name.)
* @param name 要檢查的Name內容。(Name content to check.)
* @return 檢查是否通過,如果沒有通過返回錯誤信息。(Check if it passes, and return an error message if it fails.)
* @throws IOException
* @throws ServerCloneException
*/
public FormValidation doCheckName(@QueryParameter String name)throws IOException, ServerCloneException{
	if(name.length() == 0){
	    return FormValidation.error(Messages.SlaveParameterDefinition_DescriptorImpl_errors_missingName());
	}

	return FormValidation.ok();
}

注意一定是“doCheck”+“要檢查的參數名稱”形式定義方法名稱。

創建構建參數后構建按鈕就變為Build with Parameters了。

Alt text

負責展現參數的頁面是在項目目錄/src/main/resources/com/bluersw/SlaveParameterDefinition/index.jelly中,該文件中定義了一個下拉菜單顯示Jenkins中的所有Slave服務器的名稱,每次選擇Slave服務器后JS腳本會調用服務器端的方法更新默認值,下次用戶再構建就不用從新選擇了。

Alt text

<?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"
		 xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
	<st:adjunct includes="com.bluersw.jquery"/>
	<st:adjunct includes="com.bluersw.slaveParameter"/>
	<j:set var="instance" value="${it}" />
	<j:set var="descriptor" value="${it.descriptor}"/>
	<f:entry title="${it.name}" description="${it.description}">
		<div name="parameter" id="${it.divId}" style="white-space:nowrap" >
			<input type="hidden" name="name" value="${it.name}" />
			<f:select id="slaveParameterSelect" field="value" default="${it.defaultValue}" style="width:auto;"/>
			<div id="result_message"></div>
		</div>
		<script type="text/javascript">
			var parentDiv = jQuery('#${it.divId}');
			var requestBasicUrl = "${h.getCurrentDescriptorByNameUrl()}/${it.descriptor.descriptorUrl}/setDefaultValue?name=${it.name}";
			bindOnChange(parentDiv,requestBasicUrl);
		</script>
	</f:entry>
</j:jelly>
<j:set var="descriptor" value="${it.descriptor}"/>

沒有這句會報錯,導入jquery需要先添加一個jelly文件指定腳本文件位置,再用<st:adjunct includes="com.bluersw.jquery"/>導入到頁面中。

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
	<script src="${it.packageUrl}/javascript/jQuery-3.5.1-min.js" type="text/javascript" />
</j:jelly>

該文件位置在項目目錄/src/main/resources/com/bluersw/jquery.jelly,所以includes="com.bluersw.jquery"是類似命名空間的位置定位,其他js腳本導入也是類似方法。

<f:select/>元素的數據綁定,是根據該元素的field內容,調用DescriptorImpl靜態類的方法實現,該方法命名必須是"doFill"+要綁定數據的頁面元素field屬性值+"Items"(示例中select元素的field的值是value):

/**
* 在項目構建頁面設置參數時調用,將所有可以用於構建的服務器名稱,綁定到下拉菜單中讓用戶選擇,方法名稱必須是"doFill"+要綁定數據的頁面元素field屬性值+"Items"。(Called when setting parameters on the project construction page, bind all server names that can be used for construction to the drop-down menu for the user to choose, the method name must be "doFill" + field attribute value of the page element to be bound data + "Items ".)
* @param job 當前項目的構建任務。(The build task of the current project.)
* @param name Slave Server Parameter的名稱。(The name of the Slave Server Parameter.)
* @return Slave名稱列表是Select元素,返回此元素的內容。(The Slave name list is the Select element, and returns the content of this element.)
*/
public ListBoxModel doFillValueItems(@AncestorInPath Job job, @QueryParameter String name)

name參數的值來自頁面中的input元素:

<input type="hidden" name="name" value="${it.name}" />

類似表單提交你需要什么元素的值就在參數中聲明即可(@QueryParameter String 元素名稱)。

用戶選擇Slave服務器名稱后js腳本會用ajax調用服務器端接口更新該參數對象的默認值,服務器端接口的命名必須是"do"+"方法名":

/**
* 客戶端選擇Slave服務器時JS腳本調用的服務器端方法,作用是更新此參數的默認值,以便於方便用戶下次項目構建時不需要再次選擇Slave服務器。方法名一定是"do"+"方法名"。(When the client selects the Slave server, the server-side method called by the JS script is to update the default value of this parameter, so that the user does not need to select the Slave server again when the project is built next time.The method name must be "do" + "method name".)
* @param job 當前項目的構建任務。(The build task of the current project.)
* @param name Slave Server Parameter的名稱。(The name of the Slave Server Parameter.)
* @param value 用戶選擇的Slave服務器的名稱。(The name of the slave server selected by the user.)
* @return 操作結果說明。(Explanation of operation result.)
*/
public String doSetDefaultValue(@AncestorInPath Job job, @QueryParameter String name, @QueryParameter String value)

js調用地址是.../setDefaultValue?name=xxx&value=xxx,調用函數是:

jQuery.noConflict();

function bindOnChange(parent,requestBasicUrl){
	var selectE = parent.find('#slaveParameterSelect');
	var messageD = parent.find('#result_message');
	selectE.change(function(){
		requestUrl = requestBasicUrl + "&value=" + jQuery(this).children('option:selected').val();
		jQuery.ajax({url:requestUrl,success:function(result){
			messageD.text(result);
			}})
		}
	)
}

因為"$"符號和jelly的數據綁定符號沖突,所以用jQuery.noConflict();換成jQuery,該文件在/src/main/resources/com/bluersw/javascript/slave-parameter.js,每次選擇都會調用DescriptorImpl類的doSetDefaultValue方法更新默認值。

選擇參數點擊“構建”按鈕之后,會將頁面元素的值都通過Json格式傳到服務器端調用SlaveParameterDefinition類的createValue方法,創建參數返回值后續交給構建腳本使用:

/**
* 創建Slave服務器參數的參數結果對象(Create parameter result object for Slave server parameter)
* @param staplerRequest StaplerRequest對象(StaplerRequest object)
* @param jsonObject Slave服務器參數的結果對象,Json格式(Slave Server Parameter result object, Json format)
* @return 參數結果對象 (Parameter result object)
*/
@CheckForNull
@Override
public ParameterValue createValue(StaplerRequest staplerRequest, JSONObject jsonObject)

參數jsonObject中有name和value連個頁面元素的值,這樣就可以創建用於腳本使用的變量和值的健值對了。

總結:

SlaveParameterDefinition負責創建參數類對象、綁定屬性類數據、創建構建參數結果對象(參數值),DescriptorImpl內部靜態類負責創建SlaveParameterDefinition對象、實現頁面元素的綁定方法、實現頁面請求的接口方法、驗證用戶輸入、顯示該構建參數的名稱。

其他兩個類內容很少請看完整的項目代碼:

  • SlaveParameterValue:是構建的參數的返回值,用參數名稱和選擇的值組成。
  • SlaveParameterRebuild:是在構建結果“參數”頁面查看的內容。

因為Jenkins已經取消了術語Slave,所以項目改名為Agent Server Parameter。

完整項目代碼:jenkinsci/agent-server-parameter-plugin

測試

添加構建參數:

Alt text

構建腳本:

node{
    print params['slave-name']
}

選擇構建服務器:

Alt text

構建結果:

由用戶 unknown or anonymous 啟動
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /Users/sunweisheng/Documents/HomeCode/slave-server-parameter-plugin/work/workspace/Test-pip
[Pipeline] {
[Pipeline] echo
master
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

文章原文


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM