program story

Jenkins 파이프 라인 NotSerializableException : groovy.json.internal.LazyMap

inputbox 2020. 12. 28. 08:08
반응형

Jenkins 파이프 라인 NotSerializableException : groovy.json.internal.LazyMap


해결 : S. Richmond의 답변에 감사드립니다 . 내가 설정 해제하는 데 필요한 모든 의 저장지도 groovy.json.internal.LazyMap변수 무효 의미 유형 envServersobject사용 후를.

추가 :이 오류를 검색하는 사람들은 readJSON대신 Jenkins 파이프 라인 단계를 사용하는 데 관심이있을 수 있습니다 . 여기에서 자세한 정보를 찾으 십시오 .


Jenkins Pipeline을 사용하여 json 문자열로 작업에 전달되는 사용자로부터 입력을 받으려고합니다. 그런 다음 Pipeline은 slurper를 사용하여이를 구문 분석하고 중요한 정보를 선택합니다. 그런 다음 해당 정보를 사용하여 서로 다른 작업 매개 변수와 동시에 하나의 작업을 여러 번 실행합니다.

아래 코드를 추가 할 때까지 "## 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

나는 오늘 직접 이것을 만났고 일부 무차별 대입을 통해 그것을 해결하는 방법과 잠재적 인 이유를 모두 알아 냈습니다.

그 이유부터 시작하는 것이 가장 좋습니다.

Jenkins에는 서버 재부팅을 통해 모든 작업을 중단, 일시 중지 및 재개 할 수있는 패러다임이 있습니다. 이를 위해서는 파이프 라인과 데이터가 완전히 직렬화되어야합니다. IE는 모든 상태를 저장할 수 있어야합니다. 마찬가지로 빌드에서 노드와 하위 작업 사이의 전역 변수 상태를 직렬화 할 수 있어야합니다. 이것이 제가 여러분과 저에게 일어나고 있다고 생각하고 추가 빌드 단계를 추가 할 때만 발생하는 이유입니다.

어떤 이유로 든 JSONObject는 기본적으로 직렬화 할 수 없습니다. 나는 자바 개발자가 아니기 때문에 슬프게도 주제에 대해 더 이상 말할 수 없습니다. Groovy와 Jenkins에 얼마나 적용 가능한지 모르겠지만 어떻게 올바르게 수정할 수 있는지에 대한 답변이 많이 있습니다. 자세한 정보는 이 게시물참조하십시오 .

해결 방법 :

방법을 안다면 어떻게 든 JSONObject를 직렬화 할 수 있습니다. 그렇지 않으면 전역 변수가 해당 유형이 아닌지 확인하여 해결할 수 있습니다.

objectvar 설정을 해제 하거나 메서드로 래핑하여 범위가 전역 노드가되지 않도록하십시오.


JsonSlurperClassic대신 사용하십시오 .

그루비 2.3 이후 ( 참고 : 젠킨스 2.7.1 그루비 2.4.7을 사용 ) JsonSlurper반환 LazyMap대신에 HashMap. 이것은 스레드로부터 안전 하지 않고 직렬화 할 수 JsonSlurper 없는 새로운 구현을 만듭니다 . 이로 인해 파이프 라인 DSL 스크립트의 @NonDSL 기능 외부에서는 사용할 수 없습니다.

그러나 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"]
    ...
}    

추신. 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 플러그인을 사용하는 것이 좋습니다 . 작업 공간의 파일이나 텍스트를 지원할 수 있는 readJSON단계 가 있기 때문입니다.


배열과 맵의 디코딩을 허용하는 @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에 계속 문제를 일으 킵니다.


이것은 요청 된 자세한 답변입니다.

설정되지 않은 것은 나를 위해 일했습니다.

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

구문 분석 된 응답에서 값을 읽고 더 이상 개체가 필요하지 않으면 설정을 해제합니다.


답변 중 하나를 찬성하고 싶습니다. 작업 공간 또는 텍스트에서 파일을 지원할 수있는 readJSON 단계가 있으므로 Pipeline Utility Steps 플러그인을 사용하는 것이 좋습니다. 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
}

허용 목록이나 추가 항목이 필요하지 않습니다.


The way pipeline plugin has been implemented has quite serious implications for non-trivial Groovy code. This link explains how to avoid possible problems: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

In your specific case I'd consider adding @NonCPS annotation to slurpJSON and returning map-of-maps instead of JSON object. Not only the code looks cleaner, but it's also more efficient, especially if that JSON is complex.


The other ideas in this post were helpful, but not quite all I was looking for - so I extracted the parts that fit my need and added some of my own magix...

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

Yes, as I noted in my own git commit of the code, "Wildly-ineffecient, but tiny coefficient: JSON slurp solution" (which I'm okay with for this purpose). The aspects I needed to solve:

  1. Completely get away from the java.io.NotSerializableException problem, even when the JSON text defines nested containers
  2. Work for both map and array containers
  3. Support LAX parsing (the most important part, for my situation)
  4. Easy to implement (even with the awkward nested constructors that obviate @NonCPS)

Noob mistake on my part. Moved someones code from a old pipeline plugin, jenkins 1.6? to a server running the latest 2.x jenkins.

Failed for this reason: "java.io.NotSerializableException: groovy.lang.IntRange" I kept reading and reading this post multiple times for the above error. Realized: for (num in 1..numSlaves) { IntRange - non-serializable object type.

Rewrote in simple form: for (num = 1; num <= numSlaves; num++)

All is good with the world.

I do not use java or groovy very often.

Thanks guys.


According to best practices posted on Jenkins blog (Pipeline scalability best practice), it is strongly recommended to use command-line tools or scripts for this kind of work :

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

Thus, it explains why most solutions proposed on this page are blocked by default by Jenkins security script plugin's sandbox.

The language philosophy of Groovy is closer to Bash than Python or Java. Also, it means it's not natural to do complex and heavy work in native Groovy.

Given that, I personally decided to use the following :

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

See jq Manual and Select objects with jq stackoverflow post for more help.

This is a bit counter intuitive because Groovy provides many generic methods that are not in the default whitelist.

If you decide to use Groovy language anyway for most of your work, with sandbox enabled and clean (which is not easy because not natural), I recommend you to check the whitelists for your security script plugin's version to know what are your possibilities : Script security plugin whitelists


I found more easy way in off docs for Jenkins pipeline

Work example

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.

ReferenceURL : https://stackoverflow.com/questions/37864542/jenkins-pipeline-notserializableexception-groovy-json-internal-lazymap

반응형