[持續交付實踐] pipeline使用:Shared Libraries

前言

隨着pipeline交付流水線在團隊中的推廣,使用pipeline腳本的job也迅速增長。雖然咱們已經基於公司的技術棧特色作了一個儘量通用的pipeline腳本樣例,讓搭建者只須要修改幾個賦值參數就能夠在本身的項目中應用,初衷是但願全部人能理解pipeline中的過程,但也發現一些比較麻煩的問題,好比有些人不熟悉具體的腳本拿來隨意刪改致使各類錯誤,還有就是咱們在pipeline腳本中增長一些新功能時又須要通知全部的pipeline維護人員去修改,過程很是糾結。
這時候就意味着咱們須要用到pipline的共享庫功能(Shared Libraries)了,在各類項目之間共享pipeline核心實現,以減小冗餘並保證全部job在構建的時候會調用最新的共享庫代碼 。
這篇咱們就介紹下pipeline的這個黑科技:Shared Librariesgit

目錄結構

Shared Library經過庫名稱、代碼檢索方法(如SCM)、代碼版本三個要素進行定義,庫名稱儘可能簡潔,由於它會在腳本中被調用,在編寫 Shared Library的時候,咱們須要遵循固定的代碼目錄結構。
Shared Library代碼目錄結構以下:docker

 


src目錄就是標準的Java源目錄結構。執行Pipeline時,該目錄將添加到類路徑中。
vars目錄託管定義可從Pipeline訪問的全局腳本(通常咱們能夠在這裏編寫標準化腳本)。一般,每一個.groovy文件的基本名稱應使用駝峯(camelCased)模式,.txt(若是存在)能夠包含格式化處理的文檔。
resources目錄容許libraryResource從外部庫中使用步驟來加載相關聯的非Groovy文件。目前內部庫不支持此功能。shell

 

定義全局庫

這裏只介紹全局 Shared Library的方式,經過Manage Jenkins » Configure System » Global Pipeline Libraries 的方式能夠添加一個或多個共享庫。
這些庫將全局可用,系統中的任何Pipeline均可以利用這些庫中實現的功能。而且經過配置SCM的方式,能夠保證在每次構建時獲取到指定Shared Library的最新代碼。express

 

 

動態加載庫

從2.7版本起,Pipeline: Shared Groovy Libraries plugin插件提供了一個新的參數「library」,用於在腳本中加載(non-implicit)庫
若是隻須要加載全局變量/函數感興趣(從vars/目錄中),語法很是簡單:
此後腳本中能夠訪問該庫中的任何全局變量。安全

library 'my-shared-library' 

採用此方式從src/目錄中引用類也是能夠的,不過只能動態地使用庫類(無類型檢查),從library步驟的返回值經過指定名稱訪問它們。好比static可使用相似Java的語法來調用方法:服務器

library('my-shared-library').com.mycorp.pipeline.Utils.someStaticMethod() 

使用該library步驟時,您還能夠指定一個版本,該指定版本將會覆蓋默認版本。maven

library 'my-shared-library@master' 

Shared Libraries實戰

咱們在https://testerhome.com/topics/10010已經介紹了一個項目樣例,能夠看到過程已經很是複雜,讓普通業務測試人員管理確實起來確實有點困難。
經過參數化處理後,除了一些各項目的業務變量,整個過程在全部項目都是通用的,徹底適合採用共享庫的方式進行改造,屏蔽腳本的複雜度。
在改造以前咱們勾畫了兩種思路:pipeline模塊庫和模版庫(姑且這麼叫吧)。
1.模塊庫方式
模塊庫的方式,其實就是考慮把各個stage的實現經過函數化的方式抽象出來,好比獲取代碼的stage實現咱們就抽象出codeFetch(),單元測試的 stage咱們就抽象出unitTest().
特色:業務測試人員負責維護pipeline的初始賦值和總體結構,靈活度高,可自主裁剪stage場景
不足:總體結構仍是比較複雜,須要維護的共享腳本比較多,沒法對交付流水線過程進行統一管理,Declarative Pipeline只支持script部分腳本的共享庫。
pipeline代碼樣例:函數

#!groovy library 'weiyi-pipeline-library' pipeline { agent any parameters { //repoBranch參數 string(name:'repoBranch', defaultValue: 'master', description: 'git分支名稱') //服務器選擇 choice(name: 'server',choices:'192.168.1.107,9090\n192.168.1.60,9090', description: '測試服務器列表選擇(IP,JettyPort,Name,Passwd)') string(name:'dubboPort', defaultValue: '31100', description: '測試服務器的dubbo服務端口') //單元測試代碼覆蓋率要求,各項目視要求調整參數 string(name:'lineCoverage', defaultValue: '20', description: '單元測試代碼覆蓋率要求(%),小於此值pipeline將會失敗!') //若勾選在pipelie完成後會郵件通知測試人員進行驗收 booleanParam(name: 'isCommitQA',description: '是否在pipeline完成後,郵件通知測試人員進行人工驗收',defaultValue: false ) } //環境變量,初始肯定後通常不需更改 tools { maven 'maven3' jdk 'jdk8' } ....... //pipeline的各個階段場景 stages { stage('代碼獲取') { steps { codeFetch() } } stage('單元測試') { steps { unitTest() } } } } 

共享庫代碼:gitlab

// vars/codeFetch.groovy 
def call() { echo "starting fetch code......" } 

2.模版庫方式
Declarative 1.2(released in late September, 2017),開始支持整條Declarative Pipeline做爲共享庫,使用條件以下:單元測試

Only entire pipeline`s can be defined in shared libraries as of this time. This can only be done in `vars/*.groovy, and only in a callmethod. Only one Declarative Pipeline can be executed in a single build, and if you attempt to execute a second one, your build will fail as a result. 

特色:能夠將整條declarative pipeline做爲共享庫讓各個項目調用,業務測試人員只須要維護初始化賦值參數便可。
不足:公司技術棧不統一的話,pipeline模版庫的適配能力須要比較強(好比可能會出現虛擬機/docker共存,gradle/maven共存等多種狀況),可能須要定義多個模版庫,不過這些問題經過groovy代碼邏輯上應該均可以控制。
pipeline代碼樣例(敏感信息隱藏):

#!groovy library 'weiyi-pipeline-library' def map = [:] /*參數化變量,運行時可選擇*/ //git分支名稱 map.put('repoBranch','master') //測試服務器列表選擇(IP,JettyPort,Name,Passwd) map.put('server','192.168.1.107,9090\n192.168.1.60,9090') //測試服務器的dubbo服務端口 map.put('dubboPort','31100') //單元測試代碼覆蓋率要求,各項目視要求調整參數 map.put('lineCoverage','20') /*環境變量,初始肯定後通常不需更改*/ map.put('maven','maven3') map.put('jdk','jdk8') /*常量參數,初始肯定後通常不需更改*/ map.put("isDocker",false) //項目gitlab代碼地址 map.put('REPO_URL','****') //git服務全系統只讀帳號,無需修改 map.put('CRED_ID','****') //pom.xml的相對路徑 map.put('POM_PATH','pom.xml') //生成war包的相對路徑 map.put('WAR_PATH','rpc/war/target/*.war') //測試人員郵箱地址 map.put('QA_EMAIL','***') //接口測試job名稱 map.put('ITEST_JOBNAME','Guahao_InterfaceTest_ExpertPatient') pipelineCall("maven",map) 

共享庫代碼:

#!groovy def call(String type,Map map) { if (type == "maven") { pipeline { agent any //參數化變量,目前只支持[booleanParam, choice, credentials, file, text, password, run, string]這幾種參數類型,其餘高級參數化類型還需等待社區支持 parameters { //固定設置三類pipeline場景 choice(name:'scene',choices:"scene1:完整流水線\nscene2:代碼檢查\nscene3:測試部署", description: '場景選擇,默認運行完整流水線,若是隻作開發自測可選擇代碼檢查,若是隻作環境部署可選擇測試部署') //repoBranch參數後續替換成git parameter再也不依賴手工輸入,JENKINS-46451 string(name:'repoBranch', defaultValue: "${map.repoBranch}", description: 'git分支名稱') //服務器相關參數採用了組合方式,避免屢次選擇 choice(name: 'server',choices:"${map.server}", description: '測試服務器列表選擇') string(name:'dubboPort', defaultValue: "${map.dubboPort}", description: '測試服務器的dubbo服務端口') //單元測試代碼覆蓋率要求,各項目視要求調整參數 string(name:'lineCoverage', defaultValue: "${map.lineCoverage}", description: '單元測試代碼覆蓋率要求(%),小於此值pipeline將會失敗!') //若勾選在pipelie完成後會郵件通知測試人員進行驗收 booleanParam(name: 'isCommitQA', defaultValue: false, description: '是否在pipeline完成後,郵件通知測試人員進行人工驗收') } //環境變量,初始肯定後通常不需更改 tools { maven "${map.maven}" jdk "${map.jdk}" } //常量參數,初始肯定後通常不需更改 environment{ REPO_URL="${map.REPO_URL}" //git服務全系統只讀帳號,無需修改 CRED_ID="${map.CRED_ID}" //pom.xml的相對路徑 POM_PATH="${map.POM_PATH}" //生成war包的相對路徑 WAR_PATH="${map.WAR_PATH}" //測試人員郵箱地址 QA_EMAIL="${map.QA_EMAIL}" //接口測試job名稱 ITEST_JOBNAME="${map.ITEST_JOBNAME}" } options { disableConcurrentBuilds() timeout(time: 1, unit: 'HOURS') //保持構建的最大個數 buildDiscarder(logRotator(numToKeepStr: '10')) } //pipeline的各個階段場景 stages { stage('代碼獲取') { steps { //一些初始化操做 script { //根據param.server分割獲取參數 def split=params.server.split(",") serverIP=split[0] jettyPort=split[1] serverName=split[2] serverPasswd=split[3] //場景選擇 println params.scene //單元測試運行場景 isUT=params.scene.contains('scene1:完整流水線') || params.scene.contains('scene2:代碼檢查') println "isUT="+isUT //靜態代碼檢查運行場景 isCA=params.scene.contains('scene1:完整流水線') || params.scene.contains('scene2:代碼檢查') println "isCA="+isCA //部署測試環境運行場景 isDP=params.scene.contains('scene1:完整流水線') || params.scene.contains('scene3:測試部署') println "isDP="+isDP //第三方庫安全性檢查 isDC=params.scene.contains('scene1:完整流水線') println "isDC="+isDC //接口測試運行場景 isIT=params.scene.contains('scene1:完整流水線') println "isIT="+isIT try{ wrap([$class: 'BuildUser']){ userEmail="${BUILD_USER_EMAIL},${QA_EMAIL}" user="${BUILD_USER_ID}" } }catch(exc){ userEmail="${QA_EMAIL}" user="system" } echo "starting fetchCode from ${REPO_URL}......" // Get some code from a GitHub repository git credentialsId:CRED_ID, url:REPO_URL, branch:params.repoBranch } } } stage('單元測試') { when { expression {return isUT } } } ................................................................................................如下省略幾百行 else if (type == "gradle"){ pipeline { agent any ................................................................................................繼續省略幾百行 } 

經過這種方式,整個pipeline腳本的實現和複雜度就被封裝到Shared Library中。並且若是我須要在原來的流水線基礎上增長一個新的stage,好比新增安全測試場景,也只須要庫開發人員修改共享庫的功能,須要此場景的項目在map賦值時增長相應的賦值參數便可。

後記

Shared Libraries的方式抽象了各類項目之間共享的代碼(甚至整條完整的pipeline),有效下降了業務測試人員使用pipeline腳本的複雜度。同時經過外部源代碼控制(SCM)的方式,能夠保證最新提供的庫代碼功能可被全部pipeline項目即時使用,在大團隊推廣和協做過程當中可起到很是重要的做用。

相關文章
相關標籤/搜索