Jenkinsfile/Groovy:为什么 curl 命令会导致 "bad request"
Jenkinsfile/Groovy: Why does curl command result in "bad request"
由于对 的回答,我最近了解了 withCredentials
DSL。
尝试使用@RamKamath 的答案,即以下 Jenkinsfile:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
withCredentials([usernamePassword(credentialsId: credId,
passwordVariable: 'password',
usernameVariable: 'username')]) {
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = "http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
List command = []
command.add("curl -f -L")
command.add('-u ${username}:${password}')
command.add("-H \\"Content-Type: application/json\\"")
command.add("-X POST ${url}/${commit}")
command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
}
...curl
命令本身由于报告的“错误请求”错误而失败。这是 Jenkins 控制台输出的片段:
+ curl -f -L -u ****:**** -H "Content-Type:application/json" -X POST https://bitbucket.company.com/rest/build-status/1.0/commits/0000000000000000000000000000000000000001 -d '{"state":"INPROGRESS","key":"foo_002","url":"http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"}'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 153 0 0 100 153 0 4983 --:--:-- --:--:-- --:--:-- 5100
curl: (22) The requested URL returned error: 400 Bad request
我知道 -u ****:****
是 -u
的掩码 username:password 参数。
如果我 copy/paste 那个确切的字符串变成 shell,并用真实值替换掩码值,curl
命令有效:
$ curl -f -L -u super_user:super_password -H "Content-Type:application/json" -X POST https://bitbucket.company.com/rest/build-status/1.0/commits/0000000000000000000000000000000000000001 -d '{"state":"INPROGRESS","key":"foo_002","url":"http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"}'
$
怎么了?为什么 Jenkins 执行 curl
命令会导致 error 400
/"Bad request",但手动执行时同样的命令运行正常?
请注意:按照建议,我用单引号而不是双引号将 -u ${username}:${password}
括起来。
更新:
我觉得字符串插值好像有问题,因为如果我修改 Jenkinsfile 以添加硬编码 username/password,即
command.add('-u super_user:super_password')
...而不是
command.add('-u ${username}:${password}')
...然后 curl
命令仍然像以前一样失败,即因为 error: 400 Bad request
谁能帮我找出问题所在,大概是用命令集 and/or sh()
调用?
更新
我通过删除 withCredentials()
简化了问题。即使这个简化的 curl
调用也会失败:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = "http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
List command = []
command.add("curl -f -L")
command.add('-u super_user:super_password')
command.add("-H \\"Content-Type: application/json\\"")
command.add("-X POST ${url}/${commit}")
command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
我尝试将您的脚本改编成管道项目:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def username = '********' // my real jenkins user
def password = '********' // my real jenkins pwd
String url = "http://localhost:8083/api/json"
String commit = ""
List command = []
command.add("'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L")
command.add("-u ${username}:${password}")
command.add('-H "Content-Type: application/json"') // no "..." string delimiters and escaping necessary
//command.add("-X POST ${url}/${commit}")
command.add("-X GET ${url}/${commit}") // GET instead
//command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
控制台输出
+ 'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L -u ********:******** -H 'Content-Type: application/json' -X GET http://localhost:8083/api/json/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 3232 100 3232 0 0 42667 0 --:--:-- --:--:-- --:--:-- 43675{"_class":"hudson.model.Hudson","assignedLabels":[{"name":"master"}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":
...
我记得Jenkinsfile idiosynchrasies with escaping and quotes。
与Credentials Binding Plugin
stage( "curl withCredentials" ) {
steps {
script {
withCredentials([usernamePassword(
credentialsId: 'jenkins-user',
passwordVariable: 'password',
usernameVariable: 'username')]) {
String url = "http://localhost:8083/api/json"
String commit = ""
List command = []
command.add("'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L")
command.add("-u ${username}:${password}")
command.add('-H "Content-Type: application/json"') // no "..." string delimiter and escaping necessary
//command.add("-X POST ${url}/${commit}")
command.add("-X GET ${url}/${commit}") // GET instead
//command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
控制台输出
+ 'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L -u ****:**** -H 'Content-Type: application/json' -X GET http://localhost:8083/api/json/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 3231 100 3231 0 0 42247 0 --:--:-- --:--:-- --:--:-- 42513
100 3231 100 3231 0 0 42216 0 --:--:-- --:--:-- --:--:-- 42513{"_class":"hudson.model.Hudson","assignedLabels":[{"name":"master"}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":
...
这个问题原来是一个字符串转义问题。工作解决方案——包括 withCredentials()
,这不是问题的一个因素——对我来说是:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
withCredentials([usernamePassword(credentialsId: credId,
passwordVariable: 'password',
usernameVariable: 'username')]) {
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
def cmd = "curl -f -L" +
"-u ${username}:${password} " +
"-H \"Content-Type: application/json\" " +
"-X POST ${url}/${commit} "
"-d \'${JsonOutput.toJson(dict)}\'")
sh(script: cmd)
}
}
}
}
}
}
我确信 List.join()
的某些变体会奏效 - 没有具体原因让我恢复使用 +
来连接字符串,除了我正在砍掉并解决了刚刚起作用的第一件事。在 Jenkins 中转义字符串似乎是它自己的小圈子,所以我不想在那里花费比我需要的更多的时间。
在处理此问题时出现了一些奇怪的情况:
首先,Windows 与 Unix/bash 的行为似乎有所不同:@GeroldBroser(他的帮助是无价的)能够在他的 Windows 环境中获得有效的解决方案字符串转义 closer/identical 到我原来的 post;但是我无法在我的 Unix/bash 环境中重现他的结果(Jenkins sh
调用在我的设置中使用 bash)。
最后,我的印象是记录到 Jenkins 作业控制台输出的文本 字面上 执行的内容 -- 但这似乎并不完全正确。
总结我与@GeroldBroser 的部分评论讨论:
curl
命令,当 Jenkins 的 运行 以 error: 400 Bad request
失败时,但是如果我 copy/pasted/executed 确切的 curl
命令记录在我的 Jenkins 作业控制台输出中 bash shell,成功了。
通过使用 curl
的 --trace-ascii /dev/stdout
选项,我能够发现 curl
命令,当 运行 在 bash 中成功发送 141 个字节时,但是当 Jenkins 运行 不成功时,发送了 143 个字节:额外的 2 个字节是 JSON 内容前后的前导和尾随 '
(单引号)字符。
这让我走上了疯狂的道路,到地狱的圈子,到诅咒的城堡,再到疯狂的宝座,这是 Jenkins 字符串逃逸,我最终得出了上述可行的解决方案。
值得注意:使用这个可行的解决方案,我无法再将 copy/paste curl
命令——如我的 Jenkins 作业控制台输出中所记录的那样——发送到 bash shell 并成功执行。因此,“在 Jenkins 作业控制台输出中记录的内容 完全 是 运行(即copy/pastable) 在shell."
由于对 withCredentials
DSL。
尝试使用@RamKamath 的答案,即以下 Jenkinsfile:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
withCredentials([usernamePassword(credentialsId: credId,
passwordVariable: 'password',
usernameVariable: 'username')]) {
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = "http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
List command = []
command.add("curl -f -L")
command.add('-u ${username}:${password}')
command.add("-H \\"Content-Type: application/json\\"")
command.add("-X POST ${url}/${commit}")
command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
}
...curl
命令本身由于报告的“错误请求”错误而失败。这是 Jenkins 控制台输出的片段:
+ curl -f -L -u ****:**** -H "Content-Type:application/json" -X POST https://bitbucket.company.com/rest/build-status/1.0/commits/0000000000000000000000000000000000000001 -d '{"state":"INPROGRESS","key":"foo_002","url":"http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"}'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 153 0 0 100 153 0 4983 --:--:-- --:--:-- --:--:-- 5100
curl: (22) The requested URL returned error: 400 Bad request
我知道 -u ****:****
是 -u
的掩码 username:password 参数。
如果我 copy/paste 那个确切的字符串变成 shell,并用真实值替换掩码值,curl
命令有效:
$ curl -f -L -u super_user:super_password -H "Content-Type:application/json" -X POST https://bitbucket.company.com/rest/build-status/1.0/commits/0000000000000000000000000000000000000001 -d '{"state":"INPROGRESS","key":"foo_002","url":"http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"}'
$
怎么了?为什么 Jenkins 执行 curl
命令会导致 error 400
/"Bad request",但手动执行时同样的命令运行正常?
请注意:按照建议,我用单引号而不是双引号将 -u ${username}:${password}
括起来。
更新: 我觉得字符串插值好像有问题,因为如果我修改 Jenkinsfile 以添加硬编码 username/password,即
command.add('-u super_user:super_password')
...而不是
command.add('-u ${username}:${password}')
...然后 curl
命令仍然像以前一样失败,即因为 error: 400 Bad request
谁能帮我找出问题所在,大概是用命令集 and/or sh()
调用?
更新
我通过删除 withCredentials()
简化了问题。即使这个简化的 curl
调用也会失败:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = "http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
List command = []
command.add("curl -f -L")
command.add('-u super_user:super_password')
command.add("-H \\"Content-Type: application/json\\"")
command.add("-X POST ${url}/${commit}")
command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
我尝试将您的脚本改编成管道项目:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def username = '********' // my real jenkins user
def password = '********' // my real jenkins pwd
String url = "http://localhost:8083/api/json"
String commit = ""
List command = []
command.add("'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L")
command.add("-u ${username}:${password}")
command.add('-H "Content-Type: application/json"') // no "..." string delimiters and escaping necessary
//command.add("-X POST ${url}/${commit}")
command.add("-X GET ${url}/${commit}") // GET instead
//command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
}
控制台输出
+ 'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L -u ********:******** -H 'Content-Type: application/json' -X GET http://localhost:8083/api/json/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 3232 100 3232 0 0 42667 0 --:--:-- --:--:-- --:--:-- 43675{"_class":"hudson.model.Hudson","assignedLabels":[{"name":"master"}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":
...
我记得Jenkinsfile idiosynchrasies with escaping and quotes。
与Credentials Binding Plugin
stage( "curl withCredentials" ) {
steps {
script {
withCredentials([usernamePassword(
credentialsId: 'jenkins-user',
passwordVariable: 'password',
usernameVariable: 'username')]) {
String url = "http://localhost:8083/api/json"
String commit = ""
List command = []
command.add("'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L")
command.add("-u ${username}:${password}")
command.add('-H "Content-Type: application/json"') // no "..." string delimiter and escaping necessary
//command.add("-X POST ${url}/${commit}")
command.add("-X GET ${url}/${commit}") // GET instead
//command.add("-d \\''${JsonOutput.toJson(dict)}'\\'")
sh(script: command.join(' '))
}
}
}
}
控制台输出
+ 'C:/Program Files/Git/mingw64/bin/curl.exe' -f -L -u ****:**** -H 'Content-Type: application/json' -X GET http://localhost:8083/api/json/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 3231 100 3231 0 0 42247 0 --:--:-- --:--:-- --:--:-- 42513
100 3231 100 3231 0 0 42216 0 --:--:-- --:--:-- --:--:-- 42513{"_class":"hudson.model.Hudson","assignedLabels":[{"name":"master"}],"mode":"NORMAL","nodeDescription":"the master Jenkins node","nodeName":"","numExecutors":2,"description":null,"jobs":
...
这个问题原来是一个字符串转义问题。工作解决方案——包括 withCredentials()
,这不是问题的一个因素——对我来说是:
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
def credId = "cred_id_stored_in_jenkins"
String url = "https://bitbucket.company.com/rest/build-status/1.0/commits"
String commit = '0000000000000000000000000000000000000001'
withCredentials([usernamePassword(credentialsId: credId,
passwordVariable: 'password',
usernameVariable: 'username')]) {
Map dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_002"
dict.url = http://server:8080/blue/organizations/jenkins/job/detail/job/002/pipeline"
def cmd = "curl -f -L" +
"-u ${username}:${password} " +
"-H \"Content-Type: application/json\" " +
"-X POST ${url}/${commit} "
"-d \'${JsonOutput.toJson(dict)}\'")
sh(script: cmd)
}
}
}
}
}
}
我确信 List.join()
的某些变体会奏效 - 没有具体原因让我恢复使用 +
来连接字符串,除了我正在砍掉并解决了刚刚起作用的第一件事。在 Jenkins 中转义字符串似乎是它自己的小圈子,所以我不想在那里花费比我需要的更多的时间。
在处理此问题时出现了一些奇怪的情况:
首先,Windows 与 Unix/bash 的行为似乎有所不同:@GeroldBroser(他的帮助是无价的)能够在他的 Windows 环境中获得有效的解决方案字符串转义 closer/identical 到我原来的 post;但是我无法在我的 Unix/bash 环境中重现他的结果(Jenkins sh
调用在我的设置中使用 bash)。
最后,我的印象是记录到 Jenkins 作业控制台输出的文本 字面上 执行的内容 -- 但这似乎并不完全正确。
总结我与@GeroldBroser 的部分评论讨论:
curl
命令,当 Jenkins 的 运行 以 error: 400 Bad request
失败时,但是如果我 copy/pasted/executed 确切的 curl
命令记录在我的 Jenkins 作业控制台输出中 bash shell,成功了。
通过使用 curl
的 --trace-ascii /dev/stdout
选项,我能够发现 curl
命令,当 运行 在 bash 中成功发送 141 个字节时,但是当 Jenkins 运行 不成功时,发送了 143 个字节:额外的 2 个字节是 JSON 内容前后的前导和尾随 '
(单引号)字符。
这让我走上了疯狂的道路,到地狱的圈子,到诅咒的城堡,再到疯狂的宝座,这是 Jenkins 字符串逃逸,我最终得出了上述可行的解决方案。
值得注意:使用这个可行的解决方案,我无法再将 copy/paste curl
命令——如我的 Jenkins 作业控制台输出中所记录的那样——发送到 bash shell 并成功执行。因此,“在 Jenkins 作业控制台输出中记录的内容 完全 是 运行(即copy/pastable) 在shell."