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