詹金斯管道 NotSerializableException:groovy.json.internal.LazyMap
Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap
已解决:感谢 S.Richmond 的 。我需要取消设置 all 存储的 groovy.json.internal.LazyMap
类型的映射,这意味着在使用后使变量 envServers
和 object
无效。
其他:搜索此错误的人可能有兴趣改用 Jenkins 管道步骤 readJSON
- 查找更多信息 here.
我正在尝试使用 Jenkins Pipeline 从用户那里获取作为 json 字符串传递给作业的输入。 Pipeline 然后使用 slurper 解析它,我挑选出重要信息。然后它将使用该信息多次并行地使用不同的作业参数 运行 1 个作业。
直到我在下面添加代码 "## Error when below here is added"
脚本才会 运行 正常。即使是低于该点的代码也会 运行 自己。但是合并后出现以下错误。
我应该注意到触发的作业被调用并成功执行 运行 但出现以下错误并导致主作业失败。因此,主作业不会等待触发作业的 return。我 可以 try/catch build job:
但是我希望主要工作等待触发的工作完成。
有人可以帮忙吗?如果您需要更多信息,请告诉我。
干杯
def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}
node {
stage 'Prepare';
echo 'Loading choices as build properties';
def object = slurpJSON();
def serverChoices = [];
def serverChoicesStr = '';
for (env in object) {
envName = env.name;
envServers = env.servers;
for (server in envServers) {
if (server.Select) {
serverChoicesStr += server.Server;
serverChoicesStr += ',';
}
}
}
serverChoicesStr = serverChoicesStr[0..-2];
println("Server choices: " + serverChoicesStr);
## Error when below here is added
stage 'Jobs'
build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]
}
错误:
java.io.NotSerializableException: groovy.json.internal.LazyMap
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
in field delegate
in field closures
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c
我今天 运行 自己解决了这个问题,通过一些蛮力,我已经弄清楚了如何解决它以及可能的原因。
可能最好从原因开始:
Jenkins 有一个范例,所有作业都可以通过服务器重启来中断、暂停和恢复。为实现这一点,管道及其数据必须是完全可序列化的——IE 需要能够保存所有内容的状态。同样,它需要能够序列化构建中节点和子作业之间的全局变量的状态,这就是我认为你和我正在发生的事情,以及为什么它只有在你添加额外的构建步骤时才会发生。
无论出于何种原因,默认情况下 JSONObject 不可序列化。我不是 Java 开发者,所以遗憾的是我不能就这个话题说更多。关于如何正确解决这个问题,有很多答案,但我不知道它们对 Groovy 和 Jenkins 的适用性如何。 See this post 了解更多信息。
你是如何修复它的:
如果你知道怎么做,你可能会以某种方式使 JSONObject 可序列化。否则,您可以通过确保没有全局变量属于该类型来解决它。
尝试取消设置您的 object
var 或将其包装在一个方法中,使其范围不是全局节点。
改用JsonSlurperClassic
。
自 Groovy 2.3(注意:Jenkins 2.7.1 使用 Groovy 2.4.7)JsonSlurper
returns LazyMap
而不是 HashMap
。这使得 JsonSlurper
not 线程安全和 not 可序列化的新实现。这使得它在管道 DSL 脚本中的 @NonDSL 函数之外无法使用。
但是您可以回退到支持旧 behavior 并且可以在管道脚本中安全使用的 groovy.json.JsonSlurperClassic
。
例子
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
node('master') {
def config = jsonParse(readFile("config.json"))
def db = config["database"]["address"]
...
}
ps。您仍然需要批准 JsonSlurperClassic
才能调用它。
编辑: 正如 @Sunvic 在评论中指出的那样,以下解决方案不适用于 JSON 数组。
我通过使用 JsonSlurper
处理了这个问题,然后从惰性结果中创建了一个新的 HashMap
。 HashMap
是 Serializable
.
我认为这需要将 new HashMap(Map)
和 JsonSlurper
都列入白名单。
@NonCPS
def parseJsonText(String jsonText) {
final slurper = new JsonSlurper()
return new HashMap<>(slurper.parseText(jsonText))
}
总的来说,我建议只使用可以支持工作区中的文件或文本的Pipeline Utility Steps plugin, as it has a readJSON
step。
管道插件的实现方式对重要的 Groovy 代码有相当严重的影响。 link 解释了如何避免可能出现的问题:https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables
在您的特定情况下,我会考虑向 slurpJSON
添加 @NonCPS
注释并返回地图中的地图而不是 JSON 对象。不仅代码看起来更简洁,而且效率更高,尤其是当 JSON 很复杂时。
来自@mkobit 的答案的稍微更通用的形式将允许解码数组和映射:
import groovy.json.JsonSlurper
@NonCPS
def parseJsonText(String json) {
def object = new JsonSlurper().parseText(json)
if(object instanceof groovy.json.internal.LazyMap) {
return new HashMap<>(object)
}
return object
}
注意:请注意,这只会将顶级 LazyMap 对象转换为 HashMap。任何嵌套的 LazyMap 对象仍将存在并继续导致 Jenkins 出现问题。
我在 off docs for Jenkins pipeline
中找到了更简单的方法
工作示例
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
@NonCPS
def jobs(list) {
list
.grep { it.value == true }
.collect { [ name : it.key.toString(),
branch : it.value.toString() ] }
}
node {
def params = jsonParse(env.choice_app)
def forBuild = jobs(params)
}
Due to limitations in Workflow - i.e., JENKINS-26481 - it's not really possible to use Groovy closures or syntax that depends on closures, so you can't > do the Groovy standard of using .collectEntries on a list and generating the steps as values for the resulting entries. You also can't use the standard > Java syntax for For loops - i.e., "for (String s: strings)" - and instead have to use old school counter-based for loops.
此 post 中的其他想法很有帮助,但并不是我想要的全部 - 所以我提取了适合我需要的部分并添加了一些我自己的 magix...
def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
return new JsonSlurperClassic().parseText(
new JsonBuilder(
new JsonSlurper()
.setType(JsonParserType.LAX)
.parseText(jsonText)
)
.toString()
)
}
是的,正如我在自己的 git 代码提交中指出的那样,"Wildly-ineffecient, but tiny coefficient: JSON slurp solution"(出于此目的我同意)。
我需要解决的方面:
- 完全摆脱
java.io.NotSerializableException
问题,即使 JSON 文本定义了嵌套容器
- 适用于地图和数组容器
- 支持 LAX 解析(最重要的部分,对我来说)
- 易于实施(即使使用避免
@NonCPS
的笨拙的嵌套构造函数)
我的菜鸟错误。从旧的管道插件 jenkins 1.6 移动了某人的代码?到服务器 运行 最新的 2.x jenkins.
失败原因:"java.io.NotSerializableException: groovy.lang.IntRange"
对于上面的错误,我一直阅读和阅读这个 post 多次。
实现:
for (num in 1..numSlaves) {
IntRange - 不可序列化的对象类型。
以简单形式重写:
for (num = 1; num <= numSlaves; num++)
世间万事如意。
我不经常使用 java 或 groovy。
谢谢大家。
这是要求的详细答案。
未设置对我有用:
String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2
// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null
我从已解析的响应中读取值,当我不再需要该对象时,我将其取消设置。
我想赞成其中一个答案:我建议只使用 Pipeline Utility Steps 插件,因为它有一个 readJSON 步骤,可以支持工作区中的文件或文本:https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readjson-read-json-from-files-in-the-workspace
script{
def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
def foo = readJSON text: foo_json
}
这不需要任何白名单或其他内容。
根据 Jenkins 博客 (Pipeline scalability best practice) 上发布的最佳实践,强烈建议使用命令行工具或脚本 进行此类工作:
Gotcha: especially avoid Pipeline XML or JSON parsing using Groovy’s
XmlSlurper and JsonSlurper! Strongly prefer command-line tools or
scripts.
i. The Groovy implementations are complex and as a result more brittle in Pipeline use.
ii. XmlSlurper and JsonSlurper can carry a high memory and CPU cost in pipelines
iii. xmllint and xmlstartlet are command-line tools offering XML extraction via xpath
iv. jq offers the same functionality for JSON
v. These extraction tools may be coupled to curl or wget for fetching information from an HTTP API
因此,它解释了为什么此页面上提出的大多数解决方案默认情况下都被 Jenkins 安全脚本插件的沙箱阻止。
Groovy 的语言哲学比 Python 或 Java 更接近 Bash。此外,这意味着在原生 Groovy.
中做复杂繁重的工作是不自然的
鉴于此,我个人决定使用以下内容:
sh('jq <filters_and_options> file.json')
有关更多帮助,请参阅 jq Manual and Select objects with jq Whosebug post。
这有点违反直觉,因为 Groovy 提供了许多不在默认白名单中的通用方法。
如果您决定在大部分工作中使用 Groovy 语言,并启用沙箱并进行清理(这并不容易,因为不自然),我建议您检查安全脚本插件的白名单知道你有什么可能性的版本:Script security plugin whitelists
您可以使用以下函数将 LazyMap 转换为常规 LinkedHashMap(它将保持原始数据的顺序):
LinkedHashMap nonLazyMap (Map lazyMap) {
LinkedHashMap res = new LinkedHashMap()
lazyMap.each { key, value ->
if (value instanceof Map) {
res.put (key, nonLazyMap(value))
} else if (value instanceof List) {
res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
} else {
res.put (key, value)
}
}
return res
}
...
LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);
或者更好地使用前面评论中提到的 readJSON 步骤:
Map serializableMap = readJSON text: jsonText
已解决:感谢 S.Richmond 的 groovy.json.internal.LazyMap
类型的映射,这意味着在使用后使变量 envServers
和 object
无效。
其他:搜索此错误的人可能有兴趣改用 Jenkins 管道步骤 readJSON
- 查找更多信息 here.
我正在尝试使用 Jenkins Pipeline 从用户那里获取作为 json 字符串传递给作业的输入。 Pipeline 然后使用 slurper 解析它,我挑选出重要信息。然后它将使用该信息多次并行地使用不同的作业参数 运行 1 个作业。
直到我在下面添加代码 "## Error when below here is added"
脚本才会 运行 正常。即使是低于该点的代码也会 运行 自己。但是合并后出现以下错误。
我应该注意到触发的作业被调用并成功执行 运行 但出现以下错误并导致主作业失败。因此,主作业不会等待触发作业的 return。我 可以 try/catch build job:
但是我希望主要工作等待触发的工作完成。
有人可以帮忙吗?如果您需要更多信息,请告诉我。
干杯
def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}
node {
stage 'Prepare';
echo 'Loading choices as build properties';
def object = slurpJSON();
def serverChoices = [];
def serverChoicesStr = '';
for (env in object) {
envName = env.name;
envServers = env.servers;
for (server in envServers) {
if (server.Select) {
serverChoicesStr += server.Server;
serverChoicesStr += ',';
}
}
}
serverChoicesStr = serverChoicesStr[0..-2];
println("Server choices: " + serverChoicesStr);
## Error when below here is added
stage 'Jobs'
build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]
}
错误:
java.io.NotSerializableException: groovy.json.internal.LazyMap
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
in field delegate
in field closures
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c
我今天 运行 自己解决了这个问题,通过一些蛮力,我已经弄清楚了如何解决它以及可能的原因。
可能最好从原因开始:
Jenkins 有一个范例,所有作业都可以通过服务器重启来中断、暂停和恢复。为实现这一点,管道及其数据必须是完全可序列化的——IE 需要能够保存所有内容的状态。同样,它需要能够序列化构建中节点和子作业之间的全局变量的状态,这就是我认为你和我正在发生的事情,以及为什么它只有在你添加额外的构建步骤时才会发生。
无论出于何种原因,默认情况下 JSONObject 不可序列化。我不是 Java 开发者,所以遗憾的是我不能就这个话题说更多。关于如何正确解决这个问题,有很多答案,但我不知道它们对 Groovy 和 Jenkins 的适用性如何。 See this post 了解更多信息。
你是如何修复它的:
如果你知道怎么做,你可能会以某种方式使 JSONObject 可序列化。否则,您可以通过确保没有全局变量属于该类型来解决它。
尝试取消设置您的 object
var 或将其包装在一个方法中,使其范围不是全局节点。
改用JsonSlurperClassic
。
自 Groovy 2.3(注意:Jenkins 2.7.1 使用 Groovy 2.4.7)JsonSlurper
returns LazyMap
而不是 HashMap
。这使得 JsonSlurper
not 线程安全和 not 可序列化的新实现。这使得它在管道 DSL 脚本中的 @NonDSL 函数之外无法使用。
但是您可以回退到支持旧 behavior 并且可以在管道脚本中安全使用的 groovy.json.JsonSlurperClassic
。
例子
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
node('master') {
def config = jsonParse(readFile("config.json"))
def db = config["database"]["address"]
...
}
ps。您仍然需要批准 JsonSlurperClassic
才能调用它。
编辑: 正如 @Sunvic 在评论中指出的那样,以下解决方案不适用于 JSON 数组。
我通过使用 JsonSlurper
处理了这个问题,然后从惰性结果中创建了一个新的 HashMap
。 HashMap
是 Serializable
.
我认为这需要将 new HashMap(Map)
和 JsonSlurper
都列入白名单。
@NonCPS
def parseJsonText(String jsonText) {
final slurper = new JsonSlurper()
return new HashMap<>(slurper.parseText(jsonText))
}
总的来说,我建议只使用可以支持工作区中的文件或文本的Pipeline Utility Steps plugin, as it has a readJSON
step。
管道插件的实现方式对重要的 Groovy 代码有相当严重的影响。 link 解释了如何避免可能出现的问题:https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables
在您的特定情况下,我会考虑向 slurpJSON
添加 @NonCPS
注释并返回地图中的地图而不是 JSON 对象。不仅代码看起来更简洁,而且效率更高,尤其是当 JSON 很复杂时。
来自@mkobit 的答案的稍微更通用的形式将允许解码数组和映射:
import groovy.json.JsonSlurper
@NonCPS
def parseJsonText(String json) {
def object = new JsonSlurper().parseText(json)
if(object instanceof groovy.json.internal.LazyMap) {
return new HashMap<>(object)
}
return object
}
注意:请注意,这只会将顶级 LazyMap 对象转换为 HashMap。任何嵌套的 LazyMap 对象仍将存在并继续导致 Jenkins 出现问题。
我在 off docs for Jenkins pipeline
中找到了更简单的方法工作示例
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
@NonCPS
def jobs(list) {
list
.grep { it.value == true }
.collect { [ name : it.key.toString(),
branch : it.value.toString() ] }
}
node {
def params = jsonParse(env.choice_app)
def forBuild = jobs(params)
}
Due to limitations in Workflow - i.e., JENKINS-26481 - it's not really possible to use Groovy closures or syntax that depends on closures, so you can't > do the Groovy standard of using .collectEntries on a list and generating the steps as values for the resulting entries. You also can't use the standard > Java syntax for For loops - i.e., "for (String s: strings)" - and instead have to use old school counter-based for loops.
此 post 中的其他想法很有帮助,但并不是我想要的全部 - 所以我提取了适合我需要的部分并添加了一些我自己的 magix...
def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
return new JsonSlurperClassic().parseText(
new JsonBuilder(
new JsonSlurper()
.setType(JsonParserType.LAX)
.parseText(jsonText)
)
.toString()
)
}
是的,正如我在自己的 git 代码提交中指出的那样,"Wildly-ineffecient, but tiny coefficient: JSON slurp solution"(出于此目的我同意)。 我需要解决的方面:
- 完全摆脱
java.io.NotSerializableException
问题,即使 JSON 文本定义了嵌套容器 - 适用于地图和数组容器
- 支持 LAX 解析(最重要的部分,对我来说)
- 易于实施(即使使用避免
@NonCPS
的笨拙的嵌套构造函数)
我的菜鸟错误。从旧的管道插件 jenkins 1.6 移动了某人的代码?到服务器 运行 最新的 2.x jenkins.
失败原因:"java.io.NotSerializableException: groovy.lang.IntRange" 对于上面的错误,我一直阅读和阅读这个 post 多次。 实现: for (num in 1..numSlaves) { IntRange - 不可序列化的对象类型。
以简单形式重写: for (num = 1; num <= numSlaves; num++)
世间万事如意。
我不经常使用 java 或 groovy。
谢谢大家。
这是要求的详细答案。
未设置对我有用:
String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2
// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null
我从已解析的响应中读取值,当我不再需要该对象时,我将其取消设置。
我想赞成其中一个答案:我建议只使用 Pipeline Utility Steps 插件,因为它有一个 readJSON 步骤,可以支持工作区中的文件或文本:https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readjson-read-json-from-files-in-the-workspace
script{
def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
def foo = readJSON text: foo_json
}
这不需要任何白名单或其他内容。
根据 Jenkins 博客 (Pipeline scalability best practice) 上发布的最佳实践,强烈建议使用命令行工具或脚本 进行此类工作:
Gotcha: especially avoid Pipeline XML or JSON parsing using Groovy’s XmlSlurper and JsonSlurper! Strongly prefer command-line tools or scripts.
i. The Groovy implementations are complex and as a result more brittle in Pipeline use.
ii. XmlSlurper and JsonSlurper can carry a high memory and CPU cost in pipelines
iii. xmllint and xmlstartlet are command-line tools offering XML extraction via xpath
iv. jq offers the same functionality for JSON
v. These extraction tools may be coupled to curl or wget for fetching information from an HTTP API
因此,它解释了为什么此页面上提出的大多数解决方案默认情况下都被 Jenkins 安全脚本插件的沙箱阻止。
Groovy 的语言哲学比 Python 或 Java 更接近 Bash。此外,这意味着在原生 Groovy.
中做复杂繁重的工作是不自然的鉴于此,我个人决定使用以下内容:
sh('jq <filters_and_options> file.json')
有关更多帮助,请参阅 jq Manual and Select objects with jq Whosebug post。
这有点违反直觉,因为 Groovy 提供了许多不在默认白名单中的通用方法。
如果您决定在大部分工作中使用 Groovy 语言,并启用沙箱并进行清理(这并不容易,因为不自然),我建议您检查安全脚本插件的白名单知道你有什么可能性的版本:Script security plugin whitelists
您可以使用以下函数将 LazyMap 转换为常规 LinkedHashMap(它将保持原始数据的顺序):
LinkedHashMap nonLazyMap (Map lazyMap) {
LinkedHashMap res = new LinkedHashMap()
lazyMap.each { key, value ->
if (value instanceof Map) {
res.put (key, nonLazyMap(value))
} else if (value instanceof List) {
res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
} else {
res.put (key, value)
}
}
return res
}
...
LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);
或者更好地使用前面评论中提到的 readJSON 步骤:
Map serializableMap = readJSON text: jsonText