json jenkins groovy jenkins-pipeline

Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap



jenkins-pipeline (10)

Encontré una manera más fácil en los documentos para la tubería de Jenkins

Ejemplo de trabajo

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

Debido a las limitaciones en el flujo de trabajo, es decir, JENKINS-26481 , no es realmente posible usar cierres Groovy o sintaxis que dependen de los cierres, por lo que no puede> hacer el estándar Groovy de usar .collectEntries en una lista y generar los pasos como valores para las entradas resultantes. Tampoco puede usar la sintaxis estándar> Java para los bucles For, es decir, "for (String s: strings)", y en su lugar tiene que usar bucles basados ​​en contador de la vieja escuela.

Resuelto : Gracias a la respuesta a continuación de S.Richmond. Necesitaba desarmar todos los mapas almacenados del tipo groovy.json.internal.LazyMap que significaba anular las variables envServers y el object después de su uso.

Adicional : Las personas que buscan este error pueden estar interesadas en utilizar el paso de la tubería de Jenkins readJSON en readJSON lugar; encuentre más información here .

Estoy tratando de usar Jenkins Pipeline para recibir información del usuario que se pasa al trabajo como cadena json. Pipeline analiza esto utilizando el slurper y selecciono la información importante. Luego usará esa información para ejecutar 1 trabajo varias veces en paralelo con diferentes parámetros de trabajo.

Hasta que agregue el código debajo de "## Error when below here is added" el script se ejecutará bien. Incluso el código debajo de ese punto se ejecutará solo. Pero cuando se combinan obtengo el siguiente error.

Debo señalar que se llama al trabajo desencadenado y se ejecuta con éxito, pero se produce el siguiente error y falla el trabajo principal. Debido a esto, el trabajo principal no espera la devolución del trabajo desencadenado. Podría intentar / atrapar el build job: sin embargo, quiero que el trabajo principal espere a que termine el trabajo desencadenado.

¿Alguien puede ayudar aquí? Si necesita más información, hágamelo saber.

Aclamaciones

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 org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c


Esta es la respuesta detallada que se solicitó.

Lo no establecido funcionó para mí:

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

Leí los valores de la respuesta analizada y cuando ya no necesito el objeto, lo desarmo.


Hoy mismo me encontré con esto y, gracias a la fuerza bruta, descubrí cómo resolverlo y, potencialmente, por qué.

Probablemente sea mejor comenzar con el por qué:

Jenkins tiene un paradigma en el que todos los trabajos pueden interrumpirse, pausarse y reanudarse mediante reinicios del servidor. Para lograr esto, la tubería y sus datos deben ser completamente serializables, es decir, debe poder guardar el estado de todo. Del mismo modo, debe ser capaz de serializar el estado de las variables globales entre nodos y sub-trabajos en la compilación, que es lo que creo que está sucediendo para usted y para mí y por qué solo ocurre si agrega ese paso de compilación adicional.

Por alguna razón, los JSONObject''s no son serializables por defecto. No soy un desarrollador de Java, así que no puedo decir mucho más sobre el tema tristemente. Hay muchas respuestas sobre cómo se puede solucionar esto correctamente, aunque no sé qué tan aplicables son a Groovy y Jenkins. Vea esta publicación para un poco más de información.

Cómo lo arreglas:

Si sabe cómo, puede hacer que el JSONObject sea serializable de alguna manera. De lo contrario, puede resolverlo asegurándose de que no haya variables globales de ese tipo.

Intente desarmar su object var o envolverlo en un método para que su alcance no sea global de nodo.


La forma en que se ha implementado el complemento de canalización tiene implicaciones bastante serias para el código Groovy no trivial. Este enlace explica cómo evitar posibles problemas: github.com/jenkinsci/pipeline-plugin/blob/master/…

En su caso específico, consideraría agregar la anotación @NonCPS a slurpJSON y devolver el mapa de mapas en lugar del objeto JSON. No solo el código se ve más limpio, sino que también es más eficiente, especialmente si ese JSON es complejo.


Las otras ideas en esta publicación fueron útiles, pero no todo lo que estaba buscando, así que extraje las partes que se ajustan a mis necesidades y agregué algunas de mis propias magix ...

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

Sí, como señalé en mi propio git commit del código, "Coeficientemente poco eficiente, pero pequeño coeficiente: solución de sorber JSON" (que estoy de acuerdo con este propósito). Los aspectos que necesitaba resolver:

  1. Aléjese por completo del problema java.io.NotSerializableException , incluso cuando el texto JSON define contenedores anidados
  2. Trabaja tanto para contenedores de mapas como para contenedores
  3. Admite el análisis LAX (la parte más importante, para mi situación)
  4. Fácil de implementar (incluso con los incómodos constructores anidados que obvian a @NonCPS )

Noob error de mi parte. ¿Se movió el código de alguien de un viejo plugin de canalización, jenkins 1.6? a un servidor que ejecuta las últimas 2.x jenkins.

Falló por este motivo: "java.io.NotSerializableException: groovy.lang.IntRange" Seguí leyendo y leyendo esta publicación varias veces por el error anterior. Realizado: for (num in 1..numSlaves) {IntRange - tipo de objeto no serializable.

Reescrito en forma simple: for (num = 1; num <= numSlaves; num ++)

Todo está bien con el mundo.

No uso java o groovy con mucha frecuencia.

Gracias chicos.



Una forma ligeramente más generalizada de la respuesta de @mkobit que permitiría la decodificación de matrices y mapas sería:

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 }

NOTA: Tenga en cuenta que esto solo convertirá el objeto LazyMap de nivel superior en un HashMap. Todos los objetos anidados de LazyMap seguirán allí y seguirán causando problemas con Jenkins.


Utilice JsonSlurperClassic en JsonSlurperClassic lugar.

Desde Groovy 2.3 ( nota: Jenkins 2.7.1 usa Groovy 2.4.7 ) JsonSlurper devuelve LazyMap lugar de HashMap . Esto hace que la nueva implementación de JsonSlurper no sea segura para subprocesos ni serializable. Esto lo hace inutilizable fuera de las funciones de @NonDSL en los scripts de DSL de canalización.

Sin embargo, puede recurrir a groovy.json.JsonSlurperClassic que admite behavior antiguos y podría usarse de forma segura dentro de los scripts de canalización.

Ejemplo

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

PD. Aún tendrá que aprobar JsonSlurperClassic antes de que pueda llamarse.


EDITAR: Como señaló en los comentarios, la solución a continuación no funciona tal como está para las matrices JSON.

Me JsonSlurper esto usando JsonSlurper y luego creando un nuevo HashMap partir de los resultados flojos. HashMap es Serializable .

Creo que esto requería incluir en la lista blanca tanto el new HashMap(Map) como el JsonSlurper .

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

En general, recomendaría simplemente usar el complemento Pipeline Utility Steps , ya que tiene un paso readJSON que puede admitir archivos en el espacio de trabajo o texto.