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


80

해결 : 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

이 문제를 직접 만났습니다. 아직 진전이 있었나요?
S.Richmond

답변:


71

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

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

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

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

해결 방법 :

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

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


2
감사합니다. 이것이 제가이 문제를 해결하는 데 필요한 단서입니다. 내가 이미 당신의 제안을 시도했지만 그것은 나를 다시 보게 만들었고지도의 일부를 다른 변수에 저장하고 있다고 생각하지 않았습니다. 이로 인해 오류가 발생했습니다. 그래서 설정을 해제해야했습니다. 코드에 대한 올바른 변경 사항을 포함하도록 내 질문을 수정합니다. 건배
Sunvic

1
이것은 하루에 ~ 8 번 봅니다. 이 솔루션을 구현하는 방법에 대한보다 자세한 예를 제공해 주시겠습니까?
Jordan Stefanelli

1
당신이 한 일에 따라 간단한 해결책은 없습니다. 여기에 제공된 정보와 @Sunvic이 자신의 게시물 상단에 추가 한 솔루션은 자체 코드에 대한 솔루션으로 이끌기에 충분했습니다.
S.Richmond

1
JsonSlurperClassic을 사용하는 아래 솔루션은 내가 가진 것과 똑같은 문제를 해결했으며 여기에서 승인 된 선택 일 것입니다. 이 답변에는 장점이 있지만이 특정 문제에 대한 올바른 해결책은 아닙니다.
Quartz

@JordanStefanelli 내 솔루션에 대한 코드를 게시했습니다. 보기 내 대답은 아래
닐스 엘 Himoud

127

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호출하기 전에 승인이 필요합니다 .


2
승인 방법을 알려주시겠습니까 JsonSlurperClassic?
mybecks

7
Jenkins 관리자는 Jenkins 관리»처리중인 스크립트 승인으로 이동해야합니다.
luka5z

불행하게도 나는 단지 얻을hudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic
dvtoever

13
JsonSluperClassic ..이 이름은 소프트웨어 개발의 현재 상태에 대해 많은 것을 알려줍니다
마르코스 Brigante

1
자세한 설명을 해주셔서 감사합니다. 당신은 제 시간을 많이 절약했습니다. 이 솔루션은 내 젠킨스 파이프 라인의 매력처럼 작동합니다.
Sathish Prakasam

16

편집 : 주석에서 @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단계 가 있기 때문입니다.


1
작동하지 않았습니다 Could not find matching constructor for: java.util.HashMap(java.util.ArrayList). 계속 오류가 발생했습니다 . 문서에 따르면 목록이나지도를 뱉어 내야한다고 제안합니다.지도를 반환하도록 어떻게 구성합니까?
Sunvic

@Sunvic 좋은 캐치, 우리가 파싱 한 데이터는 항상 객체이지 JSON 배열이 아닙니다. JSON 배열을 구문 분석하려고합니까?
mkobit

아 예, JSON 배열입니다.
Sunvic

젠킨스가 실행되기 때문에이 대답하고는 모두 아래, 젠킨스에, 만일 RejectedEception을 제기 그루비에서 샌드 박스 ENV
yiwen

@yiwen 관리자 화이트리스트가 필요하다고 언급했지만 그 의미에 대한 대답이 명확해질 수 있습니까?
mkobit

8

답변 중 하나를 찬성하고 싶습니다. 작업 공간 또는 텍스트에서 파일을 지원할 수있는 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
}

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


6

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

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

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

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


2

파이프 라인 플러그인이 구현 된 방식은 사소하지 않은 Groovy 코드에 상당히 심각한 영향을 미칩니다. 이 링크는 가능한 문제를 피하는 방법을 설명합니다 : https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

귀하의 특정 경우 에는 JSON 객체 대신 맵 맵에 @NonCPS주석을 추가 slurpJSON하고 반환 하는 것을 고려할 것 입니다. 코드가 깔끔해 보일뿐만 아니라 특히 JSON이 복잡한 경우 더 효율적입니다.


2

Jenkins 블로그 ( Pipeline 확장 성 모범 사례 ) 에 게시 된 모범 사례에 따르면 이러한 종류의 작업 에는 명령 줄 도구 또는 스크립트를 사용하는 것이 좋습니다 .

Gotcha : 특히 Groovy의 XmlSlurper 및 JsonSlurper를 사용하여 파이프 라인 XML 또는 JSON 구문 분석을 피하십시오! 명령 줄 도구 또는 스크립트를 강력하게 선호합니다.

나는. Groovy 구현은 복잡하고 결과적으로 파이프 라인 사용이 더 취약합니다.

ii. XmlSlurper 및 JsonSlurper는 파이프 라인에서 높은 메모리 및 CPU 비용을 수행 할 수 있습니다.

iii. xmllint 및 xmlstartlet은 xpath를 통해 XML 추출을 제공하는 명령 줄 도구입니다.

iv. jq는 JSON과 동일한 기능을 제공합니다.

v. 이러한 추출 도구는 HTTP API에서 정보를 가져 오기 위해 curl 또는 wget에 결합 될 수 있습니다.

따라서이 페이지에서 제안 된 대부분의 솔루션이 Jenkins 보안 스크립트 플러그인의 샌드 박스에 의해 기본적으로 차단되는 이유를 설명합니다.

Groovy의 언어 철학은 Python이나 Java보다 Bash에 더 가깝습니다. 또한 기본 Groovy에서 복잡하고 무거운 작업을 수행하는 것이 자연스럽지 않다는 것을 의미합니다.

이를 감안할 때 개인적으로 다음을 사용하기로 결정했습니다.

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

자세한 도움말은 jq ManualSelect objects with jq stackoverflow post 를 참조하십시오.

Groovy는 기본 화이트리스트에없는 많은 일반 메서드를 제공하기 때문에 다소 직관적입니다.

어쨌든 대부분의 작업에 Groovy 언어를 사용하기로 결정한 경우 샌드 박스를 활성화하고 깔끔하게 (자연스럽지 않기 때문에 쉽지 않음), 보안 스크립트 플러그인 버전의 화이트리스트를 확인하여 가능성이 무엇인지 알아 보는 것이 좋습니다. Script 보안 플러그인 허용 목록


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

1

이 게시물의 다른 아이디어는 도움이되었지만 제가 찾던 전부는 아니 었습니다. 그래서 저는 제 필요에 맞는 부분을 추출하고 저만의 magix를 추가했습니다.

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

네, 제가 코드의 git 커밋에서 언급했듯이, "매우 효율적이지만 작은 계수 : JSON slurp 솔루션" (이 목적은 괜찮습니다). 해결해야 할 측면 :

  1. java.io.NotSerializableExceptionJSON 텍스트가 중첩 된 컨테이너를 정의하는 경우에도 문제 에서 완전히 벗어나십시오.
  2. 지도 및 배열 컨테이너 모두에서 작동
  3. LAX 구문 분석 지원 (제 상황에서 가장 중요한 부분)
  4. 구현하기 쉬움 (을 제거하는 어색한 중첩 생성자를 사용하더라도 @NonCPS)

1

내 부분에서 멍청한 실수. 이전 파이프 라인 플러그인 인 Jenkins 1.6에서 누군가의 코드를 이동 했습니까? 최신 2.x 젠킨스를 실행하는 서버에.

이러한 이유로 실패 : "java.io.NotSerializableException : groovy.lang.IntRange"위의 오류로 인해이 게시물을 여러 번 읽고 읽었습니다. 실현 : for (1..numSlaves의 num) {IntRange-직렬화 할 수없는 객체 유형.

간단한 형식으로 다시 작성 : for (num = 1; num <= numSlaves; num ++)

모든 것이 세상에 좋다.

나는 자바 나 그루비를 자주 사용하지 않는다.

감사합니다.


0

Jenkins 파이프 라인에 대한 오프 문서 에서 더 쉬운 방법을 찾았습니다.

작품 예

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

Workflow의 제한 (예 : JENKINS-26481) 으로 인해 Groovy 클로저 또는 클로저에 의존하는 구문을 사용할 수 없으므로> 목록에서 .collectEntries를 사용하고 단계를 값으로 생성하는 Groovy 표준을 수행 할 수 없습니다. 결과 항목. 또한 For 루프에 대한 표준> Java 구문 (예 : "for (String s : strings)")을 사용할 수 없으며 대신 구식 카운터 기반 for 루프를 사용해야합니다.


1
대신 Jenkins 파이프 라인 단계 readJSON을 사용하는 것이 좋습니다 . 여기에서 자세한 정보를 찾으 십시오 .
Sunvic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.