本文分享的小
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
设计,对原有流水线只需要做很小的调整。