jenkins2 pipeline里groovy的高級用法。翻譯自:https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md
文章來自:http://www.ciandcd.com
文中的代碼來自可以從github下載: https://github.com/ciandcd
1. 在groovy里使用函數,條件控制,循環,異常捕獲等
node('remote') {
git url: 'https://github.com/jglick/simple-maven-project-with-tests.git'
def v = version()
if (v) {
echo "Building version ${v}"
}
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn -B -Dmaven.test.failure.ignore verify"
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
}
def version() {
def matcher = readFile('pom.xml') =~ '<version>(.+)</version>'
matcher ? matcher[0][1] : null
}
上面的代碼中:
def用來定義groovy變量或函數;
=~表示正則表達式的匹配;
master[0][1]表示取出匹配結果中第一個匹配項中的第一個group的值;
readFile為groovy的函數用來從workspace里讀取文件返回文件的內容,同時還可以使用writeFile來保存內容到文件,fileExists用來判斷文件是否存在;
注意 groovy腳本框下面的checkbox:use groovy sandbox,如果選中的話,groovy 腳本會在沙盒里運行有限的功能, 否則如果不是管理員運行的話會報錯或者仍然運行就的腳本,需要管理員來approve groovy script。
如果遇到RejectedAccessException權限問題,需要jenkins管理員在Manage Jenkins » In-process Script Approval中approve 權限staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter findRegex java.lang.Object java.lang.Object。
2. 本地變量的序列化
如下的代碼在運行時可能會遇到錯誤java.io.NotSerializableException: java.util.regex.Matcher,錯誤的原因是Matcher是不可序列化的類型。
node('remote') {
git url: 'https://github.com/jglick/simple-maven-project-with-tests.git'
def matcher = readFile('pom.xml') =~ '<version>(.+)</version>'
if (matcher) {
echo "Building version ${matcher[0][1]}"
}
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn -B -Dmaven.test.failure.ignore verify"
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
}
pipeline job為了支持能夠在jenkins重啟后恢復繼續運行,jenkins在后台定期地將job的運行狀態保存到硬盤。保存的動作一般在每個step結束后,或者在一些step的中間,例如sh step的中間。
jenkins保存的job的狀態,包括整個控制流程,例如局部變量,循環所在的位置,等等。正因為如此,groovy里的任何變量必須是number,string或可序列化的類型,其他的例如網絡連接等是不能夠序列化的。
如果你臨時地使用不可序列化的類型,則需要在使用完馬上釋放。如果局部變量在函數中,函數調用結束的時候局部變量也會被自動釋放。我們也可以顯示地釋放局部變量。 如下
node('remote') {
git url: 'https://github.com/jglick/simple-maven-project-with-tests.git'
def matcher = readFile('pom.xml') =~ '<version>(.+)</version>'
if (matcher) {
echo "Building version ${matcher[0][1]}"
}
matcher = null
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn -B -Dmaven.test.failure.ignore verify"
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
}
然而最安全的方法是將不可序列化的語句隔離到函數中,且在函數的前面增加屬性@NonCPS。通過這種方法pipeline將識別此函數為native且不保存對應的局部變量。另外使用了@NoCPS的函數中不能夠調用其他的pipeline steps,例如必須將readFile放到函數外面:
node('remote') {
git url: 'https://github.com/jglick/simple-maven-project-with-tests.git'
def v = version(readFile('pom.xml'))
if (v) {
echo "Building version ${v}"
}
def mvnHome = tool 'M3'
sh "${mvnHome}/bin/mvn -B -Dmaven.test.failure.ignore verify"
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
}
@NonCPS
def version(text) {
def matcher = text =~ '<version>(.+)</version>'
matcher ? matcher[0][1] : null
}
上面的加了@NoCPS的version函數將被正常的groovy運行時執行,所以任何的局部變量都是允許的。
3. 創建多線程
pipeline能夠使用parallel來同時執行多個任務。 parallel的調用需要傳入map類型作為參數,map的key為名字,value為要執行的groovy腳本。
為了測試parallel的運行,可以安裝parallel test executor插件。此插件可以將運行緩慢的測試分割splitTests。
用下面的腳本新建pipeline job:
node('remote') {
git url: 'https://github.com/jenkinsci/parallel-test-executor-plugin-sample.git'
archive 'pom.xml, src/'
}
def splits = splitTests([$class: 'CountDrivenParallelism', size: 2])
def branches = [:]
for (int i = 0; i < splits.size(); i++) {
def exclusions = splits.get(i);
branches["split${i}"] = {
node('remote') {
sh 'rm -rf *'
unarchive mapping: ['pom.xml' : '.', 'src/' : '.']
writeFile file: 'exclusions.txt', text: exclusions.join("\n")
sh "${tool 'M3'}/bin/mvn -B -Dmaven.test.failure.ignore test"
step([$class: 'JUnitResultArchiver', testResults: 'target/surefire-reports/*.xml'])
}
}
}
parallel branches
如果遇到RejectedAccessException錯誤,需要管理員approve權限staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter compareLessThan java.lang.Object java.lang.Object。
當第一次運行上面的pipeline job的時候,所有的測試順序執行。當第二次或以后執行的時候,splitTests將會將所有的測試分割為大概等價的兩份,然后兩個task並行運行。如果兩個task運行在不同的slave上,則可以看到job總的時間將會減半。
下面的等價語句用來打包pom.xml和源代碼:
archive 'pom.xml, src/'
step([$class: 'ArtifactArchiver', artifacts: 'pom.xml, src/'])
我們可以看到prallel里的語句使用了node,這意味着並行執行的任務將會在新的node/slave上執行,且使用不同的workspace,為了確保所有的node和workspace使用相同的代碼,所以才有了前面的打包archive和parallel里的解包unarchive。
上面的例子中我們可以看到同一個pipeline job里可以使用多個node,多個node會有不同的workspace,我們需要確保每個workspace的內容都是我們想要的內容。
另一個問題,如果在pipeline中使用env,環境變量的修改會在整個pipeline起作用,如果只修改parallel並行的線程的變量,可以使用withEnv。
在使用了parallel的console log里,並行的log都混在了一起,需要在job的pipeline steps頁面查看按邏輯分割的更情況的log。
4. 創建stage
默認地,pipeline的多個job可以並行地運行。使用stage可以限制job里的某些階段的並行數量。新的job用於更高的優先級,舊的job遇到stage的並行限制會直接退出。
並行性的限制有的時候很有用,例如部署到單個server,在同一時間只能有最新的一個job部署。
5. 從外部加載groovy腳本

