web-dev-qa-db-fra.com

Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap

Résolu: Merci à ci-dessous la réponse de S.Richmond. J'avais besoin de désélectionner tout les cartes stockées du type groovy.json.internal.LazyMap, ce qui signifiait annuler les variables envServers et object après utilisation.

Additional: les personnes à la recherche de cette erreur pourraient être intéressées par l’étape de pipeline Jenkins readJSON à la place - pour plus d’informations ici .


J'essaie d'utiliser Jenkins Pipeline pour prendre les entrées de l'utilisateur qui sont transmises au travail sous forme de chaîne json. Pipeline analyse ensuite cette information en utilisant le slurper et je sélectionne les informations importantes. Il utilisera ensuite ces informations pour exécuter un travail plusieurs fois en parallèle avec des paramètres de travail différents.

Jusqu'à ce que j'ajoute le code ci-dessous "## Error when below here is added", le script s'exécutera correctement. Même le code situé en dessous de ce point s'exécutera tout seul. Mais une fois combiné, j'obtiens l'erreur ci-dessous.

Je dois noter que le travail déclenché est appelé et s’exécute correctement, mais l’erreur ci-dessous se produit et fait échouer le travail principal. De ce fait, le travail principal n'attend pas le retour du travail déclenché. Je pourrais essayer/intercepter le build job: mais je souhaite que le travail principal attende que le travail déclenché se termine.

Quelqu'un peut-il aider ici? Si vous avez besoin de plus d'informations, faites le moi savoir.

À votre santé

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

}

Erreur:

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
41
Sunvic

Je me suis heurté à ce problème aujourd'hui et, grâce à une force brute, j'ai compris à la fois comment le résoudre et éventuellement pourquoi.

Probablement mieux commencer par le pourquoi:

Jenkins a un paradigme où tous les travaux peuvent être interrompus, mis en pause et réactivés via des redémarrages du serveur. Pour ce faire, le pipeline et ses données doivent être entièrement sérialisables - IE, il doit pouvoir enregistrer l'état de tout. De même, il doit être capable de sérialiser l'état des variables globales entre les noeuds et les sous-travaux dans la construction, ce qui, selon moi, se produit pour vous et moi et pourquoi cela ne se produit que si vous ajoutez cette étape de construction supplémentaire.

Pour une raison quelconque, les objets JSONObject ne sont pas sérialisables par défaut. Je ne suis pas un développeur Java, donc je ne peux malheureusement pas en dire beaucoup plus sur le sujet. Il existe de nombreuses réponses sur la manière de résoudre ce problème, mais je ne sais pas dans quelle mesure elles sont applicables à Groovy et Jenkins. Voir cet article pour un peu plus d'informations.

Comment y remédier:

Si vous savez comment faire, vous pouvez éventuellement rendre le JSONObject sérialisable en quelque sorte. Sinon, vous pouvez le résoudre en vous assurant qu'aucune variable globale ne soit de ce type. 

Essayez de désactiver votre variable object ou de l'envelopper dans une méthode afin que son étendue ne soit pas globale.

32
S.Richmond

EDIT: Comme l'a souligné @Sunvic dans les commentaires, la solution ci-dessous ne fonctionne pas telle quelle pour les tableaux JSON.

J'ai résolu ce problème en utilisant JsonSlurper, puis en créant un nouveau HashMap à partir des résultats paresseux. HashMap est Serializable.

Je crois que cela nécessitait la liste blanche de la new HashMap(Map) et de la JsonSlurper.

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

Globalement, je recommanderais simplement d’utiliser le plug-in étapes de l’utilitaire Pipeline} , car il comporte une étape readJSON pouvant prendre en charge des fichiers de l’espace de travail ou du texte.

10
mkobit

La réponse de @mkobit légèrement plus générale qui permettrait le décodage de tableaux ainsi que de cartes serait la suivante:

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
}

REMARQUE: Sachez que cela ne convertira que l'objet LazyMap de niveau supérieur en HashMap. Tous les objets LazyMap imbriqués seront toujours présents et continueront de poser des problèmes avec Jenkins.

4
TomDotTom

La manière dont le plugin pipeline a été implémenté a des implications assez sérieuses pour du code non trivial Groovy. Ce lien explique comment éviter les problèmes éventuels: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

Dans votre cas particulier, je souhaiterais ajouter une annotation @NonCPS à slurpJSON et renvoyer une carte de cartes au lieu d'un objet JSON. Non seulement le code semble plus propre, mais il est également plus efficace, en particulier si le JSON est complexe.

1
Marcin Płonka

C'est la réponse détaillée qui a été demandée.

Le non réglé a fonctionné pour moi:

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

Je lis les valeurs de la réponse analysée et quand je n'ai plus besoin de l'objet, je le désactive.

1
Nils El-Himoud

Les autres idées de ce billet ont été utiles, mais pas tout à fait à la recherche de moi - j'ai donc extrait les éléments qui répondent à mes besoins et ajouté quelques-uns de mes propres magix ...

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

Oui, comme je l’ai noté dans mon propre git commit du code, "Coefficient follement inefficace, mais minuscule: solution JSON Slurp" (qui me convient à cet effet) . Les aspects dont j'avais besoin résoudre:

  1. Éliminez complètement le problème Java.io.NotSerializableException, même lorsque le texte JSON définit des conteneurs imbriqués
  2. Travailler pour les conteneurs de cartes et de tableaux
  3. Supporte l'analyse syntaxique LAX (la partie la plus importante pour ma situation)
  4. Facile à implémenter (même avec les constructeurs imbriqués qui obstruent @NonCPS)
0
Stevel

Noob erreur de ma part. Vous avez déplacé le code de quelqu'un depuis un ancien plugin de pipeline, Jenkins 1.6? sur un serveur exécutant les derniers jenkins 2.x.

Échec pour cette raison: "Java.io.NotSerializableException: groovy.lang.IntRange" J'ai continué à lire et à lire ce message à plusieurs reprises pour l'erreur ci-dessus . Réalisé: Pour (num en 1 .. numSlaves) { IntRange - type d'objet non sérialisable.

Réécrit sous une forme simple: Pour (num = 1; num <= numSlaves; num ++)

Tout va bien avec le monde.

Je n'utilise pas Java ou groovy très souvent.

Merci les gars.

0
mpechner

Je souhaite upvoter l'une des réponses: je recommanderais simplement d'utiliser le plug-in Pipeline Utility Steps, car il comporte une étape readJSON pouvant prendre en charge des fichiers de l'espace de travail ou du texte: https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps/# readjson-read-json-à partir de fichiers dans l'espace de travail

script{
  def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
  def foo = readJSON text: foo_json
}

Cela ne nécessite pas de liste blanche ou de choses supplémentaires.

0
Regnoult

J'ai trouvé un moyen plus facile dans hors docs pour le pipeline Jenkins

Exemple de travail

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

En raison des limitations de Workflow - par exemple, JENKINS-26481 -, il n’est pas vraiment possible d’utiliser des fermetures Groovy ou une syntaxe qui dépend de fermetures. les étapes en tant que valeurs pour les entrées résultantes. Vous ne pouvez pas non plus utiliser la syntaxe standard> Java pour les boucles For - c'est-à-dire "pour (String s: strings)" - et utiliser à la place des boucles for anciennes basées sur des compteurs anciens.

0
Kirill K