前陣子使用 Jenkins 幫助公司自動化建置程式,也稍微接觸了 CI/CD,畢竟 Jenkins 就是一款 CI/CD 工具。
此處的 CI 是指持續整合(Continuous Integration),而 CD 則是持續交付或持續部屬(Continuous Delivery / Continuous Deployment)。
但這篇文章僅介紹如何撰寫 Jenkins Pipeline script。
前言
Pipeline 有兩種完全不同的語法,分別是 Declarative Pipeline 與 Scripted Pipeline。雖然兩種都是寫給 Jenkins 的 Pipeline,但是無法混在一起使用,否則執行時會有各種錯誤。
不得不提,官方文件寫得很實用,只是 scripted 語法預設隱藏,必須點擊連結 Toggle Scripted Pipeline 才會顯示,這點讓我查資料時吃了些苦頭。提醒一下,以下的所有程式碼都是 Scripted Pipeline,且不打算介紹另一種語法。
基本架構
以下是從官方文件節錄出來的基本架構,想要更詳細的說明可以直接進連結,另外也有簡體中文版本的說明可以參考。個人推薦,若能力許可,直接讀英文文件較佳,比較不會產生誤解或者前後名詞對不上的狀況。
1 | node { |
雖說非強制性,但官方建議將所有 Pipeline 工作內容都放在 node(節點)裡面。實際建置時,也是由節點分配執行器與工作區給 Pipeline,一旦移除節點區塊,Pipeline 就無法做任何事情,所以終究是必須加進去的區塊。
stage(階段)也是選擇性的,如果恰當切分階段區塊,那麼在 Jenkins 使用者界面會相當清楚地展現每個階段的運作結果、花費時間與其餘詳細資訊。多次建置後,Jenkins 還會計算每個階段平均建置時間,有助於瞭解整體趨勢。
在 Scripted Pipeline 中,不需要 stages 區塊或者是 steps 區塊,直接在階段區塊中撰寫欲執行的步驟即可。
步驟
echo
跟在 Linux 相同用法,echo
顯示的文字都會被保存在 Jenkins 的建置日誌檔中,我幾乎都用來確認該階段的狀態 echo "Stage Build result: $currentBuild.result"
。
其中 $currentBuild.result
是 Jenkins 原生變數,預設值是 null。呼叫前需要自行先設定 currentBuild.result = 'SUCCESS'
、currentBuild.result = 'FAILURE'
或者 currentBuild.result = 'UNSTABLE'
,分別會顯示綠燈、紅燈或黃燈。
sh
在 Unix/Linux 系統使用終端機,如果要在 Windows 系統下使用,可以替換為 bat
。能將各種指令包進去,簡單的用法好比說 sh 'cd;mkdir ~/ExDir;touch ~/ExDir/ExFile.log'
,也能加上其他選項,例如 returnStdout: true
可以將終端機的標準輸出作為回傳值另作他用。
1 | def FILE_TAIL = null |
以上範例中,def FILE_TAIL = null
這個變數 FILE_TAIL
也可以定義在節點區塊或者階段區塊內,就只是全域變數和區域變數的差異。需要注意的是,一旦為 sh
增加選項,就必須將預設可忽略的 script
選項清楚標示,且選項之間要以逗號區隔。如果覺得選項太長、不易閱讀而想要分行,sh
括號內接受換行。如果覺得字串太長,可以使用 +
連結。
其實這個範例有點脫褲字放屁,因為不特別使用 returnStdout: true
時,sh
的標準輸出就會自動被寫入建置日誌檔,現在卻特地擷取 sh
的標準輸出,再交給 echo
寫入建置日誌檔……沒關係,後面會提到條件控制,就能對變數 FILE_TAIL
做些特別處理了!可參閱官方文件獲得更多 sh 的介紹。
另外,單引號 ''
與雙引號 ""
內的變數 ${FILE_TAIL}
會有不同的結果,需要多加留意。
語法
在官方文件的語法說明中,提到 Scripted Pipeline 才能使用的兩種語法:條件控制與異常處理。也就是其他程式語言中常見的 if…else…
以及 try…catch…finally…
,後者似乎只有部份語言支援。
異常處理
雖然官方文件將異常處理區塊放在階段區塊之中,但我個人在處理流程出現任何錯誤就取消後續所有動作時,比較喜歡拉到節點區塊之外,使用一個 try
包住所有可能會出錯的動作,如下:
1 | try { |
因為在 try
區塊中遇到任何錯誤,都會直接跳到 catch
區塊,再也不回頭,所以要根據哪些是互相關聯的動作,事先規劃要使用幾個 try
、各自範圍要多大。
此外還有 finally
區塊可以使用,適合放一些無論成功或失敗都必須執行的動作。
有時候,發生某些系統容許,可是基於種種原因而必須中止流程的動作時,能使用 error 'This is the error message!'
回傳自定義的錯誤訊息。
條件控制
到處都看得到的條件控制,一旦符合 ()
小括號中的條件,就執行 {}
大括號中的動作,否則就繼續確認下一個條件直到結束。最常見的是 if
與 else
的搭配,就是簡單明瞭的二分法。也能使用 else if
增加多個條件,讓動作更分歧,或者只使用 if
針對某條件增加動作。
條件可以用 &&
表示兩者皆須成立,以及 ||
表示其中一者成立,更加細膩地避開各種意料外的現象。以下是個簡單的例子:
1 | node { |
定義新函式
除了使用 def
定義新變數,也可以用來定義新函式,這邊直接放範例,裡面比較特別的只有 slackSend(channel:'#backup', color: colorCode, message: msg)
將 message
指定的字串作為訊息經 Slack 傳進 channel
指定的群組中,並以 color
指定的顏色強調該則訊息。
不過要使用 slackSend
之前,記得要先在 Jenkins 安裝好 Slack Plugin!當然也要事先下載 Slack,創建指定的群組。另外還有 email 跟 HipChat 的通知套件可以使用,但我沒有實作過,就不野人獻曝了。
1 | def costomizedSlackSend(String result = 'UNKNOWN') { |
總結
為了整理這篇文章,感覺又查了不少資料,對於 Scripted Pipeline 也更熟悉了些。希望下次使用時,不會太惶恐,也不要再發生「我好像有看過這個東西,但又想不起來要怎麼用」的悲劇。
附錄
1 | def FILE_TAIL = null |