Jenkins/Groovy:为什么withCredentials()引入了NotSerializableExcepti



这个问题是Groovy/Jenkins的后续问题:如何重构sh(script:"curl ...")到网址?。

虽然我打算尝试使用HTTP Request调用 REST API - 正如一位回答者所建议的那样 - 我还想专门了解/理解原始问题 RE:不可序列化性。

我减少了链接的问答中的代码,以更最低限度地演示问题.
此代码:

@Library('my-sandbox-libs@dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
try {
my_lib.v4()
}
catch(Exception e) {
echo "Jenkinsfile: ${e.toString()}"
throw e
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
}
// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection();
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
def message = JsonOutput.toJson(dict).toString()
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}

。生成 HTTP 错误 401。这是意料之中的,因为它无需必要的身份验证即可调用 Bitbucket REST API。

缺少的是"持有者:xyz"秘密文本。过去,我使用 Jenkins/Groovy 的withCredentials函数获取了这个秘密文本,如下v4()修改后的函数所示:

// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection();
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
def message = JsonOutput.toJson(dict).toString()
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
post.setRequestProperty("Authorization", "Bearer " + auth_token)
}
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}

。但我发现,特别是添加该withCredentials块会引入java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl运行时异常。

在链接的Stack Overflow问答中,一位评论者告诉我,问题与此函数中的可序列化对象与不可序列化对象有关。但是我很难理解使用withCredentials块引入了哪些不可序列化的对象。

作为参考点,当我使用curl而不是"本机"Jenkins/Groovy 函数调用相同的 REST API 时,使用相同的withCredentials-block 工作得很好。 即以下代码工作正常:

def v1() {
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
def cmd = "curl -f -L " +
"-H "Authorization: Bearer ${auth_token}" " +
"-H "Content-Type:application/json" " +
"-X POST https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d " +
"-d '${JsonOutput.toJson(dict)}'"
sh(script: cmd, returnStatus: true)
}
}

所以,总而言之,这个问题是为什么withCredentials()引入不可序列化的对象(以及什么是不可序列化的对象),这会导致使用URL和/或HttpURLConnection时出现NonSerializableException异常,并且有人解决这个问题吗?

我不是不愿意使用不同的解决方案,例如httpRequest对象,但这个问题是关于学习这个特定问题的本质,以及如何使用现有对象来解决它,即URLHttpURLConnection对象。


Update:事实证明,我无法使用httpRequest对象使用建议的替代解决方案,因为HTTP Request插件仅适用于 Jenkins 版本 2.222.4 或更高版本,我们的 Jenkins 不符合。更新我们的 Jenkins 版本超出了我的特权,基本上我需要假设无法升级 Jenkins。

我们的 Jenkins 版本是 2.190.3,它有 Groovy 版本 2.4.12。

当遇到此类问题时,我通常会尝试将调用Groovy/Java API的"低级"代码放入@NonCPS函数中。这些函数中的对象不需要可序列化,因此我们可以自由使用任何Groovy/Java API。

背景阅读:管道 CPS 方法不匹配

确保你没有从@NonCPS函数(或任何其他未标记为@NonCPS的函数)调用任何 Jenkins 管道步骤——这样的代码可能会默默失败或做意外的事情!(不过有一些"安全"功能,比如echo

由于withCredentials是一个管道步骤,因此必须从"常规"函数(未标记为@NonCPS)调用它,该函数在调用链上一级。

请注意,我将auth_token作为参数传递给v4_internal。如果你在代码中需要其他 Jenkins 变量,这些变量也应该作为参数传递。

// vars/my_lib.groovy
import groovy.json.JsonOutput
def v4() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
v4_internal(auth_token)
}
}
@NonCPS
def v4_internal( def auth_token ) {
def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection();
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
def message = JsonOutput.toJson(dict).toString()
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
post.setRequestProperty("Authorization", "Bearer " + auth_token)
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}

这很糟糕 - 我希望有人能给出更好的答案 - 但看起来我能做到这一点的唯一方法如下:

@Library('my-sandbox-libs@dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) { steps { script { echo "hello" } } }
stage( "2" ) {
steps {
script {
try {
my_lib.v5(my_lib.getBitbucketCred())
}
catch(Exception e) {
echo "Jenkinsfile: ${e.toString()}"
throw e
}
}
}
}
stage( "3" ) { steps { script { echo "world" } } }
}
}
// vars/my_lib.groovy
import groovy.json.JsonOutput
def getBitbucketCred() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
return auth_token
}
}
def v5(auth_token) {
def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection();
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
def message = JsonOutput.toJson(dict).toString()
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
req.setRequestProperty("Authorization", "Bearer " + auth_token)
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}

具体来说,我必须调用与使用URL和/或HttpURLConnection的函数的范围完全分开withCredentials()

我不知道这在 Jenkins/Groovy 中是否被认为是可以接受的,但我对无法从v5()函数本身调用withCredentials()感到不满。我也无法从v5()调用withCredentials()包装器函数。

当我尝试直接在v5()或从v5()调用的包装器函数调用withCredentials()时,我尝试了v5()和包装器函数之间的所有@NonCPS组合,但没有奏效。我还尝试在函数结束之前显式地将URLHttpURLConnection对象设置为null(如Jenkins/Groovy:为什么withCredentials()引入NotSerializableException异常?),但这也没有奏效。

如果这是唯一的解决方案,我会对 Jenkins/Groovy 感到失望。这感觉就像是对如何选择组织他的代码的人为限制。


更新了更多详细信息以响应@daggett:

RE:直接从my_lib.v5()调用withCredentials()或从包装函数调用它,让我们从mylib.groovy设置开始,如下所示(让我也借此机会给函数起更好的名字):

def withCredWrapper() {
withCredentials([string(credentialsId: 'bitbucket_cred_id',
variable: 'auth_token')]) {
return auth_token
}
}
def callRestFunc() {
def post = new URL("https://bitbucket.company.com/rest/build-status/1.0/commits/86c36485c0cbf956a62cbc1c370f1f3eecc8665d").openConnection();
def dict = [:]
dict.state = "INPROGRESS"
dict.key = "foo_42"
dict.url = "http://url/to/nowhere"
def message = JsonOutput.toJson(dict).toString()
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
// version 1:
withCredentials([string(credentialsId: 'bb_auth_bearer_token_cred_id',
variable: 'auth_token')]) {
post.setRequestProperty("Authorization", "Bearer " + auth_token)
}
// version 2:
//post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if (postRC.equals(200)) {
println(post.getInputStream().getText());
}
}

使用上面的代码,函数callRestFunc()可以直接调用withCredentials(),如上所述,也可以通过包装函数withCredWrapper()间接调用,即:

...
// version 1:
//withCredentials([string(credentialsId: 'bb_auth_bearer_token_cred_id',
//                        variable: 'auth_token')]) {
//  post.setRequestProperty("Authorization", "Bearer " + auth_token)
//}
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...

此外,@NonCPS可以应用于withCredWrapper()callRestFunc()之一,两者兼而有之,或两者都不应用于。

以下是所有 8 种组合的具体故障:

1.

def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}

失败:Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

阿拉伯数字。

def withCredWrapper() {
...
}
@NonCPS
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}

故障:expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_tokenJenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

3.

@NonCPS
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}

失败:Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

4.

@NonCPS
def withCredWrapper() {
...
}
@NonCPS
def callRestFunc() {
...
// version 1:
withCredentials(...)
...
}

故障:expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_tokenJenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

5.

def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}

失败:Jenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

6.

def withCredWrapper() {
...
}
@NonCPS
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}

失败:expected to call my_lib.callRestFunc but wound up catching my_lib.withCredWrapper; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/

7.

@NonCPS
def withCredWrapper() {
...
}
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}

故障:expected to call my_lib.withCredWrapper but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/ Masking supported pattern matches of $auth_tokenJenkinsfile: java.io.NotSerializableException: sun.net.www.protocol.https.HttpsURLConnectionImpl

8.

@NonCPS
def withCredWrapper() {
...
}
@NonCPS
def callRestFunc() {
...
// version 2:
post.setRequestProperty("Authorization", "Bearer " + withCredWrapper())
...
}

失败:expected to call my_lib.callRestFunc but wound up catching withCredentials; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/

相关内容

  • 没有找到相关文章

最新更新