Jenkins管道NotSerializableException:groovy.json.internal.LazyMap

时间:2016-06-16 16:21:27

标签: json jenkins groovy jenkins-pipeline

解决:感谢S.Richmond的below answer。我需要取消设置{strong>所有存储的groovy.json.internal.LazyMap类型地图,这意味着在使用后无效变量envServersobject

其他:搜索此错误的人可能有兴趣使用Jenkins管道步骤readJSON - 找到更多信息here

我正在尝试使用Jenkins Pipeline从用户那里获取输入,该输入作为json字符串传递给作业。然后Pipeline使用slurper解析它,我挑选出重要的信息。然后,它将使用该信息与不同的作业参数并行多次运行1个作业。

在我添加"## Error when below here is added"下面的代码之前,脚本运行正常。即使是该点下面的代码也会自行运行。但合并后我得到以下错误。

我应该注意,触发的作业被调用并且确实成功运行但是发生了以下错误并且失败了主要作业。因此,主要工作不会等待触发作业的返回。我可以尝试/捕捉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

12 个答案:

答案 0 :(得分:81)

改为使用JsonSlurperClassic

由于Groovy 2.3(注意:Jenkins 2.7.1使用Groovy 2.4.7 JsonSlurper返回LazyMap而不是HashMap。这使得JsonSlurper 线程安全的新实现和可序列化。这使得它在管道DSL脚本中的@NonDSL函数之外无法使用。

但是,您可以回退到支持旧behaviorgroovy.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

答案 1 :(得分:38)

我今天遇到了这个问题,通过一些暴力,我已经想出了如何解决它以及可能的原因。

可能最好从这个原因开始:

Jenkins有一个范例,可以通过服务器重新启动来中断,暂停和恢复所有作业。为了实现这一点,管道及其数据必须是完全可序列化的 - IE需要能够保存所有内容的状态。同样,它需要能够序列化构建中节点和子作业之间的全局变量状态,这就是我认为你和我发生的事情,以及为什么只有在你添加额外的构建步骤时才会发生这种情况。 p>

无论出于何种原因,JSONObject默认情况下不可序列化。我不是Java开发人员,所以我不能不遗余力地谈论这个话题。虽然我不知道他们对Groovy和Jenkins有多适用,但有很多答案可以解决这个问题。 See this post获取更多信息。

如何修复它:

如果你知道如何,你可以以某种方式使JSONObject可序列化。否则,您可以通过确保没有该类型的全局变量来解决它。

尝试取消设置object var或将其包装在方法中,使其范围不是节点全局。

答案 2 :(得分:10)

编辑:正如评论中@Sunvic所指出的,以下解决方案对JSON阵列不起作用。

我使用JsonSlurper处理此问题,然后根据延迟结果创建新的HashMapHashMapSerializable

我认为这需要new HashMap(Map)JsonSlurper的白名单。

@NonCPS
def parseJsonText(String jsonText) {
  final slurper = new JsonSlurper()
  return new HashMap<>(slurper.parseText(jsonText))
}

总的来说,我建议您只使用Pipeline Utility Steps plugin,因为它有一个readJSON step,可以支持工作区或文本中的文件。

答案 3 :(得分:4)

来自@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出现问题。

答案 4 :(得分:3)

这是要求的详细答案。

这种情况对我有用:

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

我从解析的响应中读取值,当不再需要该对象时,我将其取消设置。

答案 5 :(得分:2)

管道插件的实现方式对非平凡的Groovy代码具有非常严重的影响。此链接说明了如何避免可能出现的问题:https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

在您的具体情况下,我会考虑将@NonCPS注释添加到slurpJSON并返回map-of-maps而不是JSON对象。不仅代码看起来更干净,而且效率也更高,特别是如果JSON很复杂。

答案 6 :(得分:2)

您可以使用以下函数将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

答案 7 :(得分:1)

我的菜鸟犯错了。从旧的管道插件jenkins 1.6移走了某人的代码?到运行最新的2.x jenkins的服务器。

由于以下原因而失败:“ java.io.NotSerializableException:groovy.lang.IntRange” 对于以上错误,我一直多次阅读和阅读这篇文章。 已实现: 为(1..numSlaves中的数字){ IntRange-不可序列化的对象类型。

以简单形式重写: 为(num = 1; num <= numSlaves; num ++)

世界一切都好。

我不经常使用Java或groovy。

谢谢大家。

答案 8 :(得分:1)

我想回答一个答案:我建议只使用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
}

这不需要任何白名单或其他内容。

答案 9 :(得分:1)

根据Jenkins博客(Pipeline scalability best practice)上发布的最佳做法,强烈建议使用命令行工具或脚本来进行此类工作:

  

Gotcha:尤其要避免使用Groovy的管道XML或JSON解析   XmlSlurper和JsonSlurper!非常喜欢命令行工具或   脚本。

     

i。 Groovy的实现很复杂,因此在管道使用中更加脆弱。

     

ii。 XmlSlurper和JsonSlurper可以在管道中承载较高的内存和CPU成本

     

iii。 xmllint和xmlstartlet是通过xpath提供XML提取的命令行工具

     

iv。 jq为JSON提供相同的功能

     

v。这些提取工具可以与curl或wget结合使用,以从HTTP API中获取信息

因此,它解释了为什么默认情况下,Jenkins安全脚本插件的沙箱会阻止此页面上提出的大多数解决方案。

Groovy的语言哲学比Python或Java更接近Bash。而且,这意味着在本机Groovy中进行复杂而繁重的工作是不自然的。

鉴于此,我个人决定使用以下内容:

sh('jq <filters_and_options> file.json')

有关更多帮助,请参见jq ManualSelect objects with jq stackoverflow post

这有点直观,因为Groovy提供了许多不在默认白名单中的通用方法。

如果您决定在大部分工作中仍然使用Groovy语言,并且启用并清理了沙箱(这并不容易,因为不自然),我建议您检查安全脚本插件版本的白名单,以了解您使用的语言是什么可能性:Script security plugin whitelists

答案 10 :(得分:0)

我在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)
}
  

由于工作流程的限制 - 即JENKINS-26481 - 实际上不可能使用依赖于闭包的Groovy闭包或语法,所以你不能&gt;执行Groovy标准,在列表中使用.collectEntries并生成步骤作为结果条目的值。你也不能使用标准&gt; For循环的Java语法 - 即“for(String s:strings)” - 而是必须使用旧的基于计数器的for循环。

答案 11 :(得分:0)

这篇文章中的其他想法很有帮助,但并不是我想要的所有 - 所以我提取了符合我需要的部分并添加了一些我自己的魔法......

def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
    return new JsonSlurperClassic().parseText(
        new JsonBuilder(
            new JsonSlurper()
                .setType(JsonParserType.LAX)
                .parseText(jsonText)
        )
        .toString()
    )
}

是的,正如我在我自己的git提交的代码中提到的那样,&#34; Wildly-ineffecient,但是微小的系数:JSON slurp解决方案&#34; (我没关系为了这个目的)。 我需要解决的方面:

  1. 完全摆脱java.io.NotSerializableException问题,即使JSON文本定义了嵌套容器
  2. 适用于地图和数组容器
  3. 支持LAX解析(对我来说最重要的部分)
  4. 易于实现(即使使用笨拙的嵌套构造函数来避免@NonCPS