由于Pipeline在一個(gè)組織中越來越多的項(xiàng)目被采用,普遍的模式很可能會(huì)出現(xiàn)。通常,在各種項(xiàng)目之間共享Pipeline的部分是有用的,以減少冗余并保持代碼“DRY” 。
Pipeline支持創(chuàng)建“共享庫”,可以在外部源代碼控制存儲(chǔ)庫中定義并加載到現(xiàn)有的Pipeline中。
共享庫使用名稱,源代碼檢索方法(如SCM)以及可選的默認(rèn)版本進(jìn)行定義。該名稱應(yīng)該是一個(gè)簡(jiǎn)短的標(biāo)識(shí)符,因?yàn)樗鼘⒃谀_本中使用。
該版本可以被該SCM所了解; 例如,分支,標(biāo)簽和提交hashes都為Git工作。您還可以聲明腳本是否需要顯式請(qǐng)求該庫(詳見下文),或默認(rèn)情況下是否存在。此外,如果您在Jenkins配置中指定版本,則可以阻止腳本選擇不同的版本。
指定SCM的最佳方法是使用已經(jīng)特別更新的SCM插件,以支持新的API來檢出任意命名版本(現(xiàn)代SCM選項(xiàng))。在撰寫本文時(shí),最新版本的Git和Subversion插件支持此模式; 其他人應(yīng)該遵循。
如果您的SCM插件尚未集成,則可以選擇Legacy SCM并選擇所提供的任何內(nèi)容。在這種情況下,您需要${library.yourLibName.version}
在SCM的配置中包含 某處,以便在結(jié)帳時(shí)插件將擴(kuò)展此變量以選擇所需的版本。例如,對(duì)于Subversion,您可以將Repository URL設(shè)置為https://svnserver/project/${library.yourLibName.version}
,然后使用諸如trunk
or branches/dev
或之類的版本tags/1.0
共享庫存儲(chǔ)庫的目錄結(jié)構(gòu)如下所示:
(root)
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
+- resources # resource files (external libraries only)
| +- org
| +- foo
| +- bar.json # static helper data for org.foo.Bar
該src目錄應(yīng)該像標(biāo)準(zhǔn)的Java源目錄結(jié)構(gòu)。執(zhí)行Pipeline時(shí),該目錄將添加到類路徑中。
該vars目錄托管定義可從Pipeline訪問的全局變量的腳本。通常,每個(gè)*.groovy文件的基本名稱應(yīng)該是Groovy(?Java)標(biāo)識(shí)符camelCased。匹配*.txt(如果存在)可以包含通過系統(tǒng)配置的標(biāo)記格式化程序處理的文檔(所以可能真的是HTML,Markdown等,盡管txt需要擴(kuò)展)。
這些目錄中的Groovy源文件與Scripted Pipeline中的“CPS轉(zhuǎn)換”相同。
甲resources目錄允許libraryResource從外部庫中使用步驟來加載相關(guān)聯(lián)的非Groovy文件。目前內(nèi)部庫不支持此功能。
保留根目錄下的其他目錄,以備將來進(jìn)行增強(qiáng)
根據(jù)用例,有幾個(gè)可以定義共享庫的地方。管理Jenkins?配置系統(tǒng)?全局Pipeline庫 可以配置所需的許多庫。
由于這些庫將全局可用,系統(tǒng)中的任何Pipeline都可以利用這些庫中實(shí)現(xiàn)的功能。
這些庫被認(rèn)為是“受信任的”:他們可以在Java,Groovy,Jenkins內(nèi)部API,Jenkins插件或第三方庫中運(yùn)行任何方法。這允許您定義將各個(gè)不安全的API封裝在更高級(jí)別的包裝器中的庫,以便從任何Pipeline使用。請(qǐng)注意,任何能夠?qū)⑻峤坏皆揝CM存儲(chǔ)庫的人都可以無限制地訪問Jenkins。您需要總體/ RunScripts權(quán)限來配置這些庫(通常這將授予Jenkins管理員)。
創(chuàng)建的任何文件夾可以具有與其關(guān)聯(lián)的共享庫。此機(jī)制允許將特定庫范圍限定為文件夾或子文件夾內(nèi)的所有Pipeline。
基于文件夾的庫不被認(rèn)為是“受信任的”:它們像Groovy sandbox 一樣運(yùn)行,就像典型的Pipeline一樣。
其他插件可能會(huì)添加在運(yùn)行中定義庫的方法。例如, GitHub分支源插件提供了一個(gè)“GitHub組織文件夾”項(xiàng),它允許腳本使用不受信任的庫,例如github.com/someorg/somerepo
沒有任何其他配置。在這種情況下,指定的GitHub存儲(chǔ)庫將從master
分支中使用匿名檢出進(jìn)行加載。
標(biāo)記為加載的共享庫隱式允許Pipeline立即使用由任何此類庫定義的類或全局變量。要訪問其他共享庫,Jenkinsfile需要使用@Library注釋,指定庫的名稱:
@Library('my-shared-library') _
/* Using a version specifier, such as branch, tag, etc */
@Library('my-shared-library@1.0') _
/* Accessing multiple libraries with one statement */
@Library(['my-shared-library', 'otherlib@abc1234']) _
注釋可以在腳本中的任何地方,Groovy允許注釋。當(dāng)引用類庫(包含src/
目錄)時(shí),通常會(huì)在import
語句上注釋:
@Library('somelib')
import com.mycorp.pipeline.somelib.UsefulClass
對(duì)于僅定義全局變量(vars/)的共享庫或 Jenkinsfile僅需要全局變量的 共享庫,注釋 模式@Library('my-shared-library') _可能有助于保持代碼簡(jiǎn)潔。實(shí)質(zhì)上,import該符號(hào)不是注釋不必要的語句_。
不推薦import使用全局變量/函數(shù),因?yàn)檫@將強(qiáng)制編譯器解釋字段和方法,static 即使它們是實(shí)例。在這種情況下,Groovy編譯器可能會(huì)產(chǎn)生混亂的錯(cuò)誤消息。
在編譯腳本之前,在開始執(zhí)行之前解析和加載庫。這允許Groovy編譯器了解在靜態(tài)類型檢查中使用的符號(hào)的含義,并允許它們?cè)谀_本中的類型聲明中使用,例如:
@Library('somelib')
import com.mycorp.pipeline.somelib.Helper
int useSomeLib(Helper helper) {
helper.prepare()
return helper.count()
}
echo useSomeLib(new Helper('some text'))
然而,全局變量在運(yùn)行時(shí)解決。
從2.7版Pipeline:共享Groovy庫插件,有一個(gè)新的選項(xiàng),用于在腳本中加載(非隱式)庫:一個(gè)在構(gòu)建期間的任何時(shí)間動(dòng)態(tài)library加載庫的步驟。
如果您只對(duì)使用全局變量/函數(shù)感興趣(從vars/目錄中),語法非常簡(jiǎn)單:
library 'my-shared-library'
此后,腳本中可以訪問該庫中的任何全局變量。
從src/目錄中使用類也是可能的,但是比較棘手。而在@Library編譯之前,注釋準(zhǔn)備腳本的“classpath”,在library遇到步驟時(shí),腳本已經(jīng)被編譯。因此,您不能import或以其他方式“靜態(tài)地”引用庫中的類型。
但是,您可以動(dòng)態(tài)地使用庫類(無類型檢查),從library步驟的返回值通過完全限定名稱訪問它們。 static可以使用類似Java的語法來調(diào)用方法:
library('my-shared-library').com.mycorp.pipeline.Utils.someStaticMethod()
您還可以訪問static
字段,并調(diào)用構(gòu)造函數(shù),就像它們是指定的static
方法一樣new
:
def useSomeLib(helper) { // dynamic: cannot declare as Helper
helper.prepare()
return helper.count()
}
def lib = library('my-shared-library').com.mycorp.pipeline // preselect the package
echo useSomeLib(lib.Helper.new(lib.Constants.SOME_TEXT))
例如,當(dāng)勾選“加載隱式”時(shí),或者如果Pipeline僅以名稱引用庫,則使用配置的共享庫的“默認(rèn)版本” @Library('my-shared-library') _。如果“默認(rèn)版本” 沒有定義,Pipeline必須指定一個(gè)版本,例如 @Library('my-shared-library@master') _。
如果在共享庫的配置中啟用了“允許默認(rèn)版本被覆蓋”,則@Library注釋也可以覆蓋為庫定義的默認(rèn)版本。這樣還可以在必要時(shí)從不同的版本加載帶有“負(fù)載加載”的庫。
使用該library步驟時(shí),您還可以指定一個(gè)版本:
library 'my-shared-library@master'
由于這是一個(gè)常規(guī)步驟,所以該版本可以 與注釋一樣計(jì)算而不是常量; 例如:
library "my-shared-library@$BRANCH_NAME"
將使用與多分支相同的SCM分支來加載庫Jenkinsfile
。另一個(gè)例子,你可以通過參數(shù)選擇一個(gè)庫:
properties([parameters([string(name: 'LIB_VERSION', defaultValue: 'master')])])
library "my-shared-library@${params.LIB_VERSION}"
請(qǐng)注意,該library步驟可能不會(huì)用于覆蓋隱式加載庫的版本。它在腳本啟動(dòng)時(shí)已經(jīng)加載,給定名稱的庫可能不會(huì)被加載兩次。
指定SCM的最佳方法是使用已經(jīng)特別更新的SCM插件,以支持新的API來檢出任意命名版本(現(xiàn)代SCM選項(xiàng))。在撰寫本文時(shí),Git和Subversion插件的最新版本支持此模式。
尚未更新以支持共享庫所需的較新功能的SCM插件仍可通過Legacy SCM選項(xiàng)使用。在這種情況下,包括${library.yourlibrarynamehere.version}可以為該特定SCM插件配置branch / tag / ref的任何位置。這可以確保在檢索庫的源代碼期間,SCM插件將擴(kuò)展此變量以檢出庫的相應(yīng)版本。
如果您僅@在library步驟中指定庫名稱(可選地,隨后使用版本),Jenkins將查找該名稱的預(yù)配置庫。(或者在github.com/owner/repo自動(dòng)庫的情況下,它將加載。)
但是您也可以動(dòng)態(tài)地指定檢索方法,在這種情況下,不需要在Jenkins中預(yù)定義庫。這是一個(gè)例子:
library identifier: 'custom-lib@master', retriever: modernSCM(
[$class: 'GitSCMSource',
remote: 'git@git.mycorp.com:my-jenkins-utils.git',
credentialsId: 'my-private-key'])
為了您的SCM的精確語法,最好參考流水線語法。
請(qǐng)注意,在這些情況下必須指定庫版本。
在基層,任何有效的 Groovy代碼 都可以使用。不同的數(shù)據(jù)結(jié)構(gòu),實(shí)用方法等,如:
// src/org/foo/Point.groovy
package org.foo;
// point in 3D space
class Point {
float x,y,z;
}
庫類不能直接調(diào)用諸如shor的步驟git。然而,它們可以實(shí)現(xiàn)除封閉類之外的方法,這些方法又調(diào)用Pipeline步驟,例如:
// src/org/foo/Zot.groovy
package org.foo;
def checkOutFrom(repo) {
git url: "git@github.com:jenkinsci/${repo}"
}
然后可以從腳本Pipeline中調(diào)用它:
def z = new org.foo.Zot()
z.checkOutFrom(repo)
這種做法有局限性; 例如,它阻止了超類的聲明。
或者,一組steps可以顯式傳遞給庫類,構(gòu)造函數(shù)或只是一種方法:
package org.foo
class Utilities implements Serializable {
def steps
Utilities(steps) {this.steps = steps}
def mvn(args) {
steps.sh "${steps.tool 'Maven'}/bin/mvn -o ${args}"
}
}
當(dāng)在類上保存狀態(tài)時(shí),如上面所述,類必須實(shí)現(xiàn) Serializable
接口。這樣可確保使用該類的Pipeline,如下面的示例所示,可以在Jenkins中正確掛起并恢復(fù)。
@Library('utils') import org.foo.Utilities
def utils = new Utilities(steps)
node {
utils.mvn 'clean package'
}
如果庫需要訪問全局變量,例如env,那些應(yīng)該以類似的方式顯式傳遞給庫類或方法。
而不是將許多變量從腳本Pipeline傳遞到庫中,
package org.foo
class Utilities {
static def mvn(script, args) {
script.sh "${script.tool 'Maven'}/bin/mvn -s ${script.env.HOME}/jenkins.xml -o ${args}"
}
}
上面的示例顯示了腳本被傳遞到一個(gè)static
方法,從腳本Pipeline調(diào)用如下:
@Library('utils') import static org.foo.Utilities.*
node {
mvn this, 'clean package'
}
在內(nèi)部,vars目錄中的腳本作為單例按需實(shí)例化。這允許在單個(gè).groovy文件中定義多個(gè)方法或?qū)傩裕@些文件彼此交互,例如:
// vars/acme.groovy
def setName(value) {
name = value
}
def getName() {
name
}
def caution(message) {
echo "Hello, ${name}! CAUTION: ${message}"
}
在上面,name
不是指一個(gè)字段(即使你把它寫成this.name
!),而是一個(gè)根據(jù)需要?jiǎng)?chuàng)建的條目Script.binding
。要清楚你要存儲(chǔ)什么類型的什么數(shù)據(jù),你可以改為提供一個(gè)明確的類聲明(類名稱應(yīng)符合的文件名前綴,如果只能調(diào)用Pipeline的步驟steps
或this
傳遞給類或方法,與src
上述課程一樣):
// vars/acme.groovy
class acme implements Serializable {
private String name
def setName(value) {
name = value
}
def getName() {
name
}
def caution(message) {
echo "Hello, ${name}! CAUTION: ${message}"
}
}
然后,Pipeline可以調(diào)用將在acme
對(duì)象上定義的這些方法 :
acme.name = 'Alice'
echo acme.name /* prints: 'Alice' */
acme.caution 'The queen is angry!' /* prints: 'Hello, Alice. CAUTION: The queen is angry!' */
溫習(xí)提示:在Jenkins加載并使用該庫作為成功的Pipeline運(yùn)行的一部分后,共享庫中定義的變量將僅顯示在“ 全局變量參考”(在“ Pipeline語法”下)。
共享庫還可以定義與內(nèi)置步驟類似的全局變量,例如sh
或git
。共享庫中定義的全局變量必須使用所有小寫或“camelCase”命名,以便由Pipeline正確加載。
例如,要定義sayHello
,vars/sayHello.groovy
應(yīng)該創(chuàng)建文件,并應(yīng)該實(shí)現(xiàn)一個(gè)call
方法。該call
方法允許以類似于以下步驟的方式調(diào)用全局變量:
// vars/sayHello.groovy
def call(String name = 'human') {
// Any valid steps can be called from this code, just like in other
// Scripted Pipeline
echo "Hello, ${name}."
}
然后,Pipeline將能夠引用并調(diào)用此變量:
sayHello 'Joe'
sayHello() /* invoke with default arguments */
如果調(diào)用一個(gè)塊,該call
方法將收到一個(gè) Closure
。應(yīng)明確界定類型,以澄清步驟的意圖,例如:
// vars/windows.groovy
def call(Closure body) {
node('windows') {
body()
}
}
然后,Pipeline可以像接受一個(gè)塊的任何內(nèi)置步驟一樣使用此變量:
windows {
bat "cmd /?"
}
如果您有大量類似的Pipeline,則全局變量機(jī)制提供了一個(gè)方便的工具來構(gòu)建更高級(jí)別的DSL來捕獲相似性。例如,所有的插件Jenkins構(gòu)建和以同樣的方式進(jìn)行測(cè)試,所以我們可以寫一個(gè)名為步 buildPlugin:
// vars/buildPlugin.groovy
def call(body) {
// evaluate the body block, and collect configuration into the object
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
// now build, based on the configuration provided
node {
git url: "https://github.com/jenkinsci/${config.name}-plugin.git"
sh "mvn install"
mail to: "...", subject: "${config.name} plugin build", body: "..."
}
}
假設(shè)腳本已被加載為 全局共享庫或 文件夾級(jí)共享庫 ,結(jié)果Jenkinsfile
將會(huì)更加簡(jiǎn)單:
Jenkinsfile (Scripted Pipeline)
buildPlugin {
name = 'git'
}
通過使用注釋,可以使用通常位于Maven Central中的第三方Java庫 從受信任的庫代碼中使用@Grab。 有關(guān)詳細(xì)信息,請(qǐng)參閱 Grape文檔,但簡(jiǎn)單地說:
@Grab('org.apache.commons:commons-math3:3.4.1')
import org.apache.commons.math3.primes.Primes
void parallelize(int count) {
if (!Primes.isPrime(count)) {
error "${count} was not prime"
}
// …
}
第三方庫默認(rèn)緩存~/.groovy/grapes/在Jenkins主機(jī)上。
外部庫可以resources/使用libraryResource步驟從目錄中加載附件文件。參數(shù)是一個(gè)相對(duì)路徑名,類似于Java資源加載:
def request = libraryResource 'com/mycorp/pipeline/somelib/request.json'
該文件作為字符串加載,適合傳遞給某些API或使用保存到工作空間writeFile。
建議使用獨(dú)特的包裝結(jié)構(gòu),以便您不會(huì)意外與另一個(gè)庫沖突。
如果您使用不受信任的庫發(fā)現(xiàn)構(gòu)建中的錯(cuò)誤,只需單擊Replay鏈接即可嘗試編輯其一個(gè)或多個(gè)源文件,并查看生成的構(gòu)建是否按預(yù)期方式運(yùn)行。一旦您對(duì)結(jié)果感到滿意,請(qǐng)從構(gòu)建狀態(tài)頁面執(zhí)行diff鏈接,并將diff應(yīng)用于庫存儲(chǔ)庫并提交。
(即使庫要求的版本是分支,而不是像標(biāo)簽一樣的固定版本,重播的構(gòu)建將使用與原始版本完全相同的修訂版本:庫源將不會(huì)被重新簽出。)
受信任的庫不支持重放。Replay中目前不支持修改資源文件。
更多建議: