本文分享的小
Tips
是在我前面的文章DevOps建設之基於釘釘OA審批流的自動化上線中提到的,當通過API
自動觸發Jenkins Pipeline
流水線執行時,如果原來的流水線中定義了在構建正式開始后還需要接收用戶input
的步驟,想要自動繞過或自動執行input
的方法
1、pipeline input概述
首先回過頭再來看看pipeline input
的語法及功能,參考我之前總結的pipeline input語法
input
指令stage
允許使用input step
提示輸入。在stage
將暫停任何后options
已被應用,並在進入前agent
塊為stage
或評估when
的條件stage
。如果input
批准,stage
則將繼續。作為input
提交的一部分提供的任何參數將在其余的環境中可用stage
。
可選項
- message
必需的,這將在用戶提交時顯示給用戶input
- id
可選標識符input
,默認為stage
名稱 - ok
input
表單上“確定”按鈕的可選文本 - submitter
可選的逗號分隔列表,這些列表允許用戶提交此用戶或外部組名input
。默認為允許任何用戶。 - submitterParameter
環境變量的可選名稱,用該submitter
名稱設置(如果存在) - parameters
提示提交者提供的可選參數列表。請參閱Pipeline parameters以獲取更多信息
2、背景概述
基於上面的語法描述,我這里線上發布流水線中input
的功能僅僅是需要用戶進行確認,所以沒有傳遞任何參數,通過這種簡單的input
控制及timeout
超時機制,實現了用戶選擇參數並點擊開始構建后需要在60秒內二次確認的功能,流水線的部分內容如下
stage('Deploy to prod'){
when {
environment name: 'PerformType', value: 'Deploy'
}
steps{
timeout(time:60, unit:'SECONDS') {
input "確認要部署到線上環境嗎?"
script{
try {
...
}
catch (exc) {
...
throw(exc)
}
}
}
}
}
到這里問題就產生了,input
的過程是在流水線運行過程中動態出現的,如果是想要在釘釘OA
審批通過后自動通過調用jenkins api
並傳入參數讓整個流水線自動執行,並且自動進行input
的確認操作或者繞過input
,應該怎么做呢?
3、推理及測試
剛開始沒有任何思路,唯一想到的辦法就是把input
的過程從pipeline
中去除掉,這樣就沒有任何煩惱了
但是為了保留原有pipeline
設計的完整性,顯然這種做法不夠友好,只是避開了這個難點,是不可取的
通過查找發現這方面的資料很少,最終有用的資料如下
input
語法中可選字段包含id
,每個input
步驟都有一個唯一的ID
。在生成的URL
中可以使用它來繼續或中止
例如,可以使用特定的ID
來機械地響應來自某些外部過程/工具的輸入
這篇文章中講到了如何通過Jenkins REST API
恢復暫停的管道?作為參考起到了一定幫助
為了完成整個自動化input
的過程,具體的演進流程如下
3.1 通過Crumb安全操作Jenkins
Crumb
指的是Jenkins
的CSRF token
,Jenkins
服務器為了阻止不安全的跨域請求,默認開啟了CSRF
保護,參考Jenkins遠程API訪問
Jenkins的CSRF配置可以在「系統管理」——> 「全局安全配置」——> 「CSRF Protection」相關配置中關閉此保護,跨站請求偽造這是一個很常見的安全問題,為了安全起見建議不關閉。如果關閉,這里的內容可以略過。
當Jenkins
開啟CSRF
保護后,可以通過固定的接口獲得一個安全的Crumb
以便於通過API
操作Jenkins
,以curl
請求為例,請求的可選方式一般是兩種,如下
方法一:
curl -u <username>:<password> 'https://jenkins.ssgeek.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
Jenkins-Crumb:dc78dfb9615fb56bbf2001fb99c64dbd3331c5e14c8d4edd54722e7ca790529e%
方法二:
curl -u <username>:<password> 'https://jenkins.ssgeek.com/crumbIssuer/api/json'
{"_class":"hudson.security.csrf.DefaultCrumbIssuer","crumb":"52d605f43328f15303c2e68eb492b9656e229ce124c2f5f2e39b6f552f54e4ac","crumbRequestField":"Jenkins-Crumb"}%
以上兩種方式都可以獲取一個Crumb
,然后就能帶着它去請求Jenkins
的API
了
curl -u <username>:<password> -X POST -H "Jenkins-Crumb:b220147dbdf3cfebbeba4c29048c2e33" -d <data> 'https://jenkins.ssgeek.com/<jenkins api url>'
3.2 通過Token安全操作Jenkins
在官方文檔的描述中有這樣一句話:API tokens are preferred instead of crumbs for CSRF protection.
意為在開啟了CSRF
的情況下,首選的是通過API token
操作而不是crumb
,這里的API token
指的就是Jenkins
中用戶的API token
可以通過「用戶」——> 「設置」——> 「API Token」——> 「添加新Token」來獲得一個api token
,有了這個Token
之后,以curl
請求為例操作Jenkins
的API
方式如下
curl -u user_id:user_api_token -X POST -d <data> 'https://jenkins.ssgeek.com/<jenkins api url>'
3.3 API操作
參考上面的文檔資料使用Jenkins REST API恢復暫停的管道
對於input
有這樣的api
接口地址可以使用,用於將輸入發送到等待的輸入步驟。url
格式如下
http://<JenkinsURL>/<JobURL>/<Build#>/input/<InputID>/submit
需要滿足的條件
-
如果
Jenkins
啟用了CSRF
保護,則您需要使用Crumb
或API Token
-
請求通過
POST
方式發送 -
需要提供參數名為
proceed
的值,並且以OK
作為參數值 -
為了提供數據,需要帶有
json
格式的參數,這些參數就是在input
階段需要接收的參數,格式為{ "parameter":[ { "name":"param1", "value":"valueOfParam1" }, { "name":"param2", "value":"valueOfParam2" } ] }
如果沒有發送有效的
json
參數,則流水線也將繼續進行,只是不會獲得任何參數(這也可能導致流水線最終執行失敗),如果成功則返回302
狀態碼並重定向到用戶界面 -
必須填寫
input id
,因此要從外部連接到的input
步驟配置唯一的id
-
也可以使用下面的url,如果流水線成功,則返回狀態碼為
200
且響應為空http://<Jenkins URL>/job/<YOUR_PROJECT>/<BUILD_NUMBER>/wfapi/inputSubmit
其他可用的api
接口地址以及作用
- 用於中止流水線
http://<Jenkins URL>/job/<YOUR_PROJECT>/<BUILD_NUMBER>/input/<INPUT_ID>/abort
- 不傳入任何參數並繼續進行流水線
http://<Jenkins URL>/job/<YOUR_PROJECT>/<BUILD_NUMBER>/input/<INPUT_ID>/proceedEmpty
對於本文中我的需求,只需要在input
執行時自動確認且無需傳入任何參數即可,因此使用的接口地址為上面的最后一種(其余接口地址未測試)
3.4 input的改造
為了實現在input
執行時自動確認,需要對流水線的input
部分進行改造,加入一個固定的id
即可
由於定義的id
都是固定的,因此可以利用腳本對所有的流水線涉及到這種input
的部分批量更新,這里就不列出具體方法了
最終我的流水線調整如下
stage('Deploy to prod'){
when {
beforeInput true
environment name: 'PerformType', value: 'Deploy'
}
options {
timeout(time:60, unit:'SECONDS')
}
input {
message "確認要部署到線上環境嗎?"
id "CustomId"
}
steps{
script{
try {
...
}
catch (exc) {
...
throw(exc)
}
}
}
}
4、自動化input的最終實現
經過上面的推理和測試,解決了通過API
自動執行input
進行流水線確認的問題
這里還剩下最后一個問題,通過測試發現,想要自動執行input
過程,其接口對應的url
地址並不是一直存在的,而是在流水線執行開始后到達input
的時候才會出現,出現時通過瀏覽器訪問查看如下
而其余時間發送請求都會返回404
狀態碼,此時是無法接收post
請求的,因此想要自動化執行input
並不只是簡單的向接口發送POST
請求了
我這里的解決思路:
在發送流水線開始執行的請求后,立即通過代碼循環請求並判斷接口地址返回的狀態碼是否是200
如果不是,那么表示流水線還沒執行到這里;如果是,就可以完美的向這個地址發送自動執行的請求了
以python
語言調用Jenkins api
為例,用到了python-jenkins
這個包,在觸發構建時使用build_job
這個方法,這個方法返回值剛好是job
任務的build number
,這恰好是接口地址組成中需要的一部分
好了,上最終的部分代碼
def auto_job_input(self, server_url, job_name, build_number):
"""
根據input階段生成的url http狀態碼,判斷當前job流水線運行的stage否進行到了input步驟
自動執行input or 繼續判斷
:param job_name:
:param build_number:
:return:
"""
# https://jenkins.ssgeek.com/job/input-demo/64/input
get_url = server_url + "/job/" + job_name + "/" + str(build_number) + "/input"
# https://jenkins.ssgeek.com/job/input-demo/64/input/CustomId/proceedEmpty
post_url = get_url + "/CustomId/proceedEmpty"
s = requests.Session()
res_code_get = s.get(url=get_url, auth=('user_id', 'user_token')).status_code
while res_code_get != 200:
res_code_get = s.get(url=get_url, auth=('user_id', 'user_token')).status_code
res_code_post = s.post(url=post_url, auth=('user_id', 'user_token'), data=None).status_code
return res_code_post
關鍵部分代碼量很少,利用request
並且攜帶認證參數進行請求,如果有大佬有更好的方案歡迎與我交流
5、小結
到這里,通過一步步推理演進,在流水線中input
的自動化執行就完美實現了,最終既實現了調用api
觸發自動構建並執行input
進行自動確認,同時也保留了原流水線的input
設計,對原有流水線只需要做很小的調整。