Jenkinsfile/Groovy:为啥 curl 命令会导致“错误请求”
Posted
技术标签:
【中文标题】Jenkinsfile/Groovy:为啥 curl 命令会导致“错误请求”【英文标题】:Jenkinsfile/Groovy: Why does curl command result in "bad request"Jenkinsfile/Groovy:为什么 curl 命令会导致“错误请求” 【发布时间】:2021-10-16 06:19:28 【问题描述】:感谢this question 的回答,我最近了解了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 控制台输出的 sn-p:
+ 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 参数。
如果我将该确切的字符串复制/粘贴到 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 以添加硬编码的用户名/密码,即
command.add('-u super_user:super_password')
...而不是
command.add('-u $username:$password')
...那么curl
命令仍然像以前一样失败,即因为error: 400 Bad request
谁能帮我找出问题所在,大概是命令程序集和/或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(' '))
【问题讨论】:
command.add(...)
在 Groovy 上下文中。根据Groovy, Strings and GString literals, Double quoted string (interpolation) 和Groovy, String Interpolation,如果您尝试使用"..."
而不是'...'
作为字符串分隔符会怎样?如果我尝试使用单引号内联管道项目来调整您的脚本,我会得到:[curl -f -L, -u $username:$password, -H \"Content-Type: application/json\", -X POST http://localhost:8083/api/json/]
.
@GeroldBroser - 试一试,即现在command.add(...)
的每个实例都是command.add("...")
。 IE。没有 command.add(...)
的实例使用单引号。但是curl
命令的失败与原始帖子中的相同。
@GeroldBroser - 试图将这个问题简化为最简单的 MVRE,我发现即使没有 withCredentials()
和变量,这个问题也是可重现的——即只有硬编码字符串的所有内容!我会相应地更新我的帖子;检查问题的最简单版本可能会有所帮助。
“没有withCredentials()
”已经是我“改编你的脚本”的一部分了。 :)
@GeroldBroser - 更新时删除了 withCredentials()
。我非常确定这是一些晦涩难懂的小字符串插值问题,这让我很头疼。但对于我的生活,我看不到它。试图在命令的正确位置获得双引号的字符串争论是地狱,我想我已经成功了——再次,因为如果我从 Jenkins 控制台输出中复制/粘贴命令到 shell, curl
命令有效!
【参考方案1】:
我尝试将您的脚本内嵌在管道项目中:
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":
...
【讨论】:
仍然失败...我注意到您正在 Windows 上进行测试,这让我想知道这个问题是否是 Windows 插入字符串与 Linux Bash 的一些细微差别。将-v
添加到curl
命令看起来很有希望:当Jenkins 运行curl 命令时,它报告Content-Length: 143
,但是当我将该命令从Jenkins 控制台输出复制/粘贴到bash
shell 时,然后是curl
报告Content-Length: 141
。因此,Jenkins“真正”执行的内容与它在控制台日志中报告的内容之间存在一些差异。
@StoneThrow 字符串插值已经在 Jenkins 内部完成。查看它打印为 cmd 行的内容。没有什么可以插入的了。 (顺便说一句,MinGW 和 Linux 的 Bash 在字符串插值方面并没有什么不同。我经历过的唯一区别是同一程序选项/参数的不同行为。)
天哪:猜猜curl --trace-ascii /dev/stdout ...
揭示了什么:当Jenkins 执行curl command
时,HTTP POST 内容包括一个前导和尾随单引号(json内容前后)!!!这很奇怪——我不知道如何“描述”这个问题:通过所有疯狂的转义序列,Jenkins 最终实际执行的内容包括 HTTP POST 中的前导和尾随单引号 _ content`...但是我将 Jenkins 控制台输出中打印的整个命令复制/粘贴到 Bash shell 中,它可以工作!
我现在正在检查链接。我希望这个问题特别不是字符串争吵/转义,因为以前的经验告诉我詹金斯是多么令人痛心! :P
终于!一个有效的解决方案!那是一场噩梦般的经历! :P 您的帮助非常宝贵 - 谢谢。我并不是要通过发布我自己的答案来贬低您的答案,但是我的工作解决方案与您的不同,因为我只能将其归结为 Windows 和 Unix/bash 环境之间的差异。如果您有兴趣,请查看我的答案,我希望它是全面的并提供一些很好的见解。【参考方案2】:
这个问题原来是一个字符串转义问题。可行的解决方案——包括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 环境中获得一个可行的解决方案,其中字符串转义更接近/与我的原始帖子相同;但是我无法在我的 Unix/bash 环境中重现他的结果(Jenkins sh
调用在我的设置中使用 bash)。
最后,我的印象是,记录到 Jenkins 作业控制台输出的文本字面意思是执行的 - 但这似乎并不完全正确。
总结我与@GeroldBroser 的部分评论讨论:curl
命令在 Jenkins 运行时以 error: 400 Bad request
失败,但如果我复制/粘贴/执行在 bash shell 中的 Jenkins 作业控制台输出中记录的确切 curl
命令,它会成功运行。
通过使用curl
的--trace-ascii /dev/stdout
选项,我发现curl
命令在bash 中成功运行时发送了141 个字节,但是当Jenkins 运行不成功时,发送了143 个字节:额外的2字节在 JSON 内容之前和之后是前导和尾随 '
(单引号)字符。
这使我走上了疯狂的道路,进入了地狱之环,到达了诅咒城堡,到达了 Jenkins 逃跑的疯狂宝座,最终我得出了上述可行的解决方案。
值得注意的是:使用这个可行的解决方案,我无法再将curl
命令(在我的 Jenkins 作业控制台输出中登录)复制/粘贴到 bash shell 并成功执行。因此,“在 Jenkins 作业控制台输出中记录的内容完全是在 shell 中运行的内容(即复制/粘贴)并不是(总是)正确的。”
【讨论】:
以上是关于Jenkinsfile/Groovy:为啥 curl 命令会导致“错误请求”的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Inquire pos 在 Fortran 90 中返回 0
不明白为啥会发生 UnboundLocalError(关闭)[重复]
不明白为啥会发生 UnboundLocalError(关闭)[重复]
为啥我不能将 std::string 与 libcurl 一起使用?
2021-06-18:已知数组arr,生成一个数组out,out的每个元素必须大于等于1,当arr[cur]>arr[cur-1]时,out[cur]>out[cur-1];当arr[cur]>arr