wake-up-neo.com

Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap

Gelöst: Danke an unter Antwort von S.Richmond. Ich musste all gespeicherte Maps des Typs groovy.json.internal.LazyMap aufheben, was bedeutete, dass die Variablen envServers und object nach der Verwendung aufgehoben wurden.

Additional: Benutzer, die nach diesem Fehler suchen, könnten an der Verwendung des Jenkins-Pipeline-Schritts readJSON interessiert sein. Weitere Informationen finden Sie hier hier .


Ich versuche, Jenkins Pipeline zu verwenden, um Eingaben vom Benutzer zu übernehmen, die als Json-String an den Job übergeben werden. Pipeline analysiert dies dann mit dem Slurper und ich nehme die wichtigen Informationen heraus. Diese Informationen werden dann verwendet, um einen Job mehrere Male parallel mit verschiedenen Jobparametern auszuführen.

Bis ich unten den Code "## Error when below here is added" hinzufüge, wird das Skript einwandfrei laufen. Sogar der Code unter diesem Punkt wird von selbst ausgeführt. Aber in Kombination bekomme ich den untenstehenden Fehler.

Ich sollte beachten, dass der ausgelöste Job aufgerufen wird und erfolgreich ausgeführt wird, der folgende Fehler jedoch auftritt und den Hauptjob fehlschlägt. Aus diesem Grund wartet der Hauptjob nicht auf die Rückgabe des ausgelösten Jobs. I könnte versuchen, den build job: abzufangen, jedoch möchte ich, dass der Hauptjob auf den ausgelösten Job wartet.

Kann hier jemand helfen? Wenn Sie weitere Informationen benötigen, lassen Sie es mich wissen.

Prost

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']]

}

Error:

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 [email protected]
41
Sunvic

Ich bin heute selbst auf dieses Thema gestoßen und durch ein paar Bruteforce habe ich herausgefunden, wie ich es lösen kann und möglicherweise warum.

Wahrscheinlich am besten, um mit dem Warum zu beginnen:

Jenkins hat ein Paradigma, bei dem alle Jobs durch Server-Neustarts unterbrochen, angehalten und wieder aufgenommen werden können. Um dies zu erreichen, müssen die Pipeline und ihre Daten vollständig serialisierbar sein - IE, sie müssen den Zustand von allem speichern können. Ebenso muss es in der Lage sein, den Status globaler Variablen zwischen Knoten und Unteraufträgen im Build zu serialisieren. Dies ist, was meiner Meinung nach für Sie und mich geschieht, und der Grund, warum er nur auftritt, wenn Sie diesen zusätzlichen Buildschritt hinzufügen.

Aus irgendeinem Grund sind JSONObjects standardmäßig nicht serialisierbar. Ich bin kein Java-Entwickler und kann leider nicht viel mehr zu diesem Thema sagen. Es gibt viele Antworten, wie man das richtig regeln kann, obwohl ich nicht weiß, wie gut sie auf Groovy und Jenkins anwendbar sind. Siehe diesen Beitrag für ein wenig mehr Informationen.

Wie Sie es beheben:

Wenn Sie wissen wie, können Sie das JSONObject möglicherweise in irgendeiner Weise serialisierbar machen. Andernfalls können Sie es auflösen, indem Sie sicherstellen, dass keine globalen Variablen von diesem Typ sind. 

Deaktivieren Sie Ihre Variable object oder binden Sie sie in eine Methode ein, sodass der Gültigkeitsbereich nicht knoten-global ist.

32
S.Richmond

Verwenden Sie stattdessen JsonSlurperClassic.

Seit Groovy 2.3 (Hinweis: Jenkins 2.7.1 verwendet Groovy 2.4.7) JsonSlurper gibt LazyMap anstelle von HashMap zurück. Dies macht die neue Implementierung von JsonSlurpernot thread sicher und not serialisierbar. Dies macht es außerhalb von @NonDSL-Funktionen in Pipeline-DSL-Skripts unbrauchbar.

Sie können jedoch auf groovy.json.JsonSlurperClassic zurückgreifen, der altes Verhalten unterstützt und in Pipeline-Skripts sicher verwendet werden kann.

Beispiel

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. Sie müssen noch JsonSlurperClassic genehmigen, bevor es aufgerufen werden kann.

76
luka5z

EDIT: Wie von @Sunvic in den Kommentaren ausgeführt, funktioniert die untenstehende Lösung für JSON-Arrays nicht wie sie ist.

Ich behandelte das, indem ich JsonSlurper verwendete und dann aus den Lazy-Ergebnissen eine neue HashMap erstellte. HashMap ist Serializable.

Ich glaube, dass dies ein Whitelisting der new HashMap(Map) und der JsonSlurper voraussetzte.

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

Insgesamt würde ich nur die Verwendung von Pipeline Utility Steps plugin empfehlen, da es über einen readJSON step verfügt, der Dateien im Arbeitsbereich oder Text unterstützen kann.

10
mkobit

Eine etwas verallgemeinerte Form der Antwort von @mkobit, die das Decodieren von Arrays sowie von Karten ermöglichen würde, wäre:

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
}

ANMERKUNG: Beachten Sie, dass dadurch nur das LazyMap-Objekt der obersten Ebene in eine HashMap konvertiert wird. Alle verschachtelten LazyMap-Objekte sind weiterhin vorhanden und verursachen weiterhin Probleme mit Jenkins.

4
TomDotTom

Die Implementierung des Pipeline-Plug-Ins hat erhebliche Auswirkungen auf nicht trivialen Groovy-Code. Dieser Link erklärt, wie Sie mögliche Probleme vermeiden können: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

In Ihrem speziellen Fall würde ich erwägen, @NonCPS-Annotation zu slurpJSON hinzuzufügen und map-of-maps anstelle eines JSON-Objekts zurückzugeben. Nicht nur der Code sieht sauberer aus, er ist auch effizienter, insbesondere wenn dieser JSON komplex ist.

1
Marcin Płonka

Dies ist die detaillierte Antwort, nach der gefragt wurde.

Das unset hat für mich funktioniert:

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

Ich lese die Werte aus der geparsten Antwort und wenn ich das Objekt nicht mehr brauche, setze ich es auf.

1
Nils El-Himoud

Die anderen Ideen in diesem Beitrag waren hilfreich, aber nicht alles, wonach ich gesucht hatte. Also habe ich die Teile herausgesucht, die zu meinem Bedürfnis passen, und etwas von meinem eigenen Magix hinzugefügt ...

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

Ja, wie ich in meinem eigenen Git-Commit des Codes festgestellt habe, "Wildly ineffecient, aber winziger Koeffizient: JSON Slurp-Lösung" (mit dem ich für diesen Zweck in Ordnung bin) . Die Aspekte, die ich brauchte lösen:

  1. Vermeiden Sie das Java.io.NotSerializableException-Problem vollständig, auch wenn der JSON-Text verschachtelte Container definiert
  2. Arbeiten Sie für Map- und Array-Container
  3. LAX-Analyse unterstützen (der wichtigste Teil für meine Situation)
  4. Einfach zu implementieren (selbst mit den unhandlich geschachtelten Konstruktoren, die @NonCPS vermeiden)
0
Stevel

Noob Fehler von meiner Seite. Someones-Code aus einem alten Pipeline-Plugin verschoben, Jenkins 1.6? zu einem Server, auf dem die neuesten 2.x-Jenkins ausgeführt werden.

Aus diesem Grund fehlgeschlagen: "Java.io.NotSerializableException: groovy.lang.IntRange" Ich habe diesen Beitrag mehrmals für den obigen Fehler gelesen und gelesen Realized: Für (num in 1 .. numSlaves) { IntRange - nicht serialisierbarer Objekttyp.

Umschreiben in einfacher Form: Für (num = 1; num <= numSlaves; num ++)

Alles ist gut mit der Welt.

Ich verwende nicht sehr oft Java oder Groovy.

Danke Leute.

0
mpechner

Ich möchte eine der Antworten stimmen: Ich würde nur das Plug-in Pipeline Utility Steps verwenden, da es über einen readJSON-Schritt verfügt, der entweder Dateien im Arbeitsbereich oder Text unterstützt: 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
}

Dies erfordert KEINE Whitelists oder zusätzliches Material.

0
Regnoult

Ich fand einen einfacheren Weg in off docs für Jenkins Pipeline

Arbeitsbeispiel

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

Aufgrund von Einschränkungen im Workflow - dh JENKINS-26481 - ist es nicht wirklich möglich, Groovy-Verschlüsse oder eine von Verschlüssen abhängige Syntax zu verwenden. Sie können also nicht den Groovy-Standard für die Verwendung von .collectEntries in einer Liste und zum Generieren verwenden die Schritte als Werte für die resultierenden Einträge. Sie können auch nicht die standardmäßige> Java-Syntax für For-Schleifen verwenden, d. H. "For (String s: strings)" - und müssen stattdessen Old School Counter-based für Schleifen verwenden.

0
Kirill K