Pipeline 擴(kuò)展共享庫

2018-08-26 10:51 更新

由于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},然后使用諸如trunkor branches/dev或之類的版本tags/1.0

目錄結(jié)構(gòu)

共享庫存儲(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庫 可以配置所需的許多庫。

Pipeline 擴(kuò)展共享庫

由于這些庫將全局可用,系統(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管理員)。

文件夾級(jí)共享庫

創(chuàng)建的任何文件夾可以具有與其關(guān)聯(lián)的共享庫。此機(jī)制允許將特定庫范圍限定為文件夾或子文件夾內(nèi)的所有Pipeline。

基于文件夾的庫不被認(rèn)為是“受信任的”:它們像Groovy sandbox 一樣運(yùn)行,就像典型的Pipeline一樣。

自動(dòng)共享庫

其他插件可能會(huì)添加在運(yùn)行中定義庫的方法。例如, GitHub分支源插件提供了一個(gè)“GitHub組織文件夾”項(xiàng),它允許腳本使用不受信任的庫,例如github.com/someorg/somerepo沒有任何其他配置。在這種情況下,指定的GitHub存儲(chǔ)庫將從master 分支中使用匿名檢出進(jìn)行加載。

使用庫

標(biāo)記為加載的共享庫隱式允許Pipeline立即使用由任何此類庫定義的類或全局變量。要訪問其他共享庫,Jenkinsfile需要使用@Library注釋,指定庫的名稱:

Pipeline 擴(kuò)展共享庫

@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í)解決。

動(dòng)態(tài)加載庫

從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插件的最新版本支持此模式。

Pipeline 擴(kuò)展共享庫

傳統(tǒng)SCM

尚未更新以支持共享庫所需的較新功能的SCM插件仍可通過Legacy SCM選項(xiàng)使用。在這種情況下,包括${library.yourlibrarynamehere.version}可以為該特定SCM插件配置branch / tag / ref的任何位置。這可以確保在檢索庫的源代碼期間,SCM插件將擴(kuò)展此變量以檢出庫的相應(yīng)版本。

Pipeline 擴(kuò)展共享庫

動(dòng)態(tài)檢索

如果您僅@在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)注意,在這些情況下必須指定庫版本。

Writing libraries

在基層,任何有效的 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的步驟stepsthis傳遞給類或方法,與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)置步驟類似的全局變量,例如shgit。共享庫中定義的全局變量必須使用所有小寫或“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 /?"
}

定義更結(jié)構(gòu)化的DSL

如果您有大量類似的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è)庫沖突。

預(yù)測(cè)庫更改

如果您使用不受信任的庫發(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中目前不支持修改資源文件。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)