Resuelto : Gracias a la siguiente respuesta de S.Richmond. Necesitaba desarmar todos los mapas almacenados del groovy.json.internal.LazyMap
tipo que significaba anular las variables envServers
y object
después de su uso.
Adicional : las personas que busquen este error podrían estar interesadas en utilizar el paso de canalización de Jenkins en su readJSON
lugar; encuentre más información aquí .
Estoy tratando de usar Jenkins Pipeline para tomar la entrada del usuario que se pasa al trabajo como una cadena json. Pipeline luego analiza esto usando el slurper y selecciono la información importante. Luego utilizará esa información para ejecutar 1 trabajo varias veces en paralelo con diferentes parámetros de trabajo.
Hasta que agregue el código debajo, "## Error when below here is added"
el script funcionará bien. Incluso el código debajo de ese punto se ejecutará por sí solo. Pero cuando se combinan, aparece el siguiente error.
Debo señalar que se llama al trabajo activado 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 activado. Yo podría tratar / catch alrededor del build job:
embargo quiero que el trabajo principal que esperar a que el trabajo disparado a fin.
¿Alguien puede ayudar aquí? Si necesita más información, hágamelo saber.
Salud
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
fuente
Respuestas:
Me encontré con esto yo mismo hoy y, a través de un poco de fuerza bruta, he descubierto 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 el reinicio del servidor. Para lograr esto, la canalización y sus datos deben ser completamente serializables; es decir, debe poder guardar el estado de todo. Del mismo modo, debe poder serializar el estado de las variables globales entre los nodos y los subtrabajos en la compilación, que es lo que creo que nos está sucediendo a usted y a mí y por qué solo ocurre si agrega ese paso de compilación adicional.
Por alguna razón, los JSONObject no se pueden serializar de forma predeterminada. No soy un desarrollador de Java, así que lamentablemente no puedo decir mucho más sobre el tema. Hay muchas respuestas sobre cómo se puede solucionar esto correctamente, aunque no sé qué tan aplicables son a Groovy y Jenkins. Ver esta publicación para obtener más información.
Cómo lo arreglas:
Si sabe cómo, posiblemente pueda hacer que JSONObject sea serializable de alguna manera. De lo contrario, puede resolverlo asegurándose de que ninguna variable global sea de ese tipo.
Intente desarmar su
object
var o envolverlo en un método para que su alcance no sea global de nodo.fuente
Úselo en su
JsonSlurperClassic
lugar.Dado que Groovy 2.3 ( nota: Jenkins 2.7.1 usa Groovy 2.4.7 )
JsonSlurper
devuelve enLazyMap
lugar deHashMap
. Esto hace que la nueva implementación deJsonSlurper
no sea segura para subprocesos y no sea serializable. Esto lo hace inutilizable fuera de las funciones de @NonDSL en los scripts DSL de canalización.Sin embargo, puede recurrir a lo
groovy.json.JsonSlurperClassic
que admite el comportamiento anterior y podría usarse de manera 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 aprobarlo
JsonSlurperClassic
antes de que se pueda llamar.fuente
JsonSlurperClassic
?hudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic
EDITAR: Como señaló @Sunvic en los comentarios, la siguiente solución no funciona como está para JSON Arrays.
Lidé con esto usando
JsonSlurper
y luego creando uno nuevo aHashMap
partir de los resultados perezosos.HashMap
esSerializable
.Creo que esto requiere tanto de la lista blanca
new HashMap(Map)
y laJsonSlurper
.@NonCPS def parseJsonText(String jsonText) { final slurper = new JsonSlurper() return new HashMap<>(slurper.parseText(jsonText)) }
En general, recomendaría usar el complemento Pipeline Utility Steps , ya que tiene un
readJSON
paso que puede admitir archivos en el espacio de trabajo o texto.fuente
Could not find matching constructor for: java.util.HashMap(java.util.ArrayList)
. La documentación sugiere que debería escupir una lista o un mapa: ¿cómo se configura para devolver un mapa?Quiero votar a favor de una de las respuestas: recomendaría usar el complemento Pipeline Utility Steps, ya que tiene un paso readJSON que puede admitir archivos en el espacio de trabajo o texto: 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 }
Esto NO requiere ninguna lista blanca o cosas adicionales.
fuente
Esta es la respuesta detallada que se pidió.
El desarmado 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
Leo los valores de la respuesta analizada y cuando ya no necesito el objeto, lo desarmo.
fuente
Una forma un poco 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. Cualquier objeto LazyMap anidado seguirá estando allí y seguirá causando problemas con Jenkins.
fuente
La forma en que se implementó el complemento de canalización tiene implicaciones bastante serias para el código Groovy no trivial. Este enlace explica cómo evitar posibles problemas: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables
En su caso específico, consideraría agregar una
@NonCPS
anotaciónslurpJSON
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.fuente
De acuerdo con las mejores prácticas publicadas en el blog de Jenkins ( mejores prácticas de escalabilidad de canalizaciones ), se recomienda encarecidamente utilizar herramientas de línea de comandos o scripts para este tipo de trabajo:
Por lo tanto, explica por qué la mayoría de las soluciones propuestas en esta página están bloqueadas de forma predeterminada por el sandbox del complemento de script de seguridad de Jenkins.
La filosofía del lenguaje de Groovy está más cerca de Bash que Python o Java. Además, significa que no es natural hacer un trabajo complejo y pesado en Groovy nativo.
Dado eso, personalmente decidí usar lo siguiente:
sh('jq <filters_and_options> file.json')
Consulte jq Manual y Select objects con jq stackoverflow post para obtener más ayuda.
Esto es un poco contrario a la intuición porque Groovy proporciona muchos métodos genéricos que no están en la lista blanca predeterminada.
Si decide utilizar el lenguaje Groovy de todos modos para la mayor parte de su trabajo, con sandbox habilitado y limpio (lo cual no es fácil porque no es natural), le recomiendo que consulte las listas blancas de la versión de su complemento de script de seguridad para saber cuáles son sus posibilidades: Script listas blancas de complementos de seguridad
fuente
Puede utilizar la siguiente función para convertir LazyMap en un LinkedHashMap normal (mantendrá el orden de los datos originales):
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);
o mejor use un paso readJSON como se notó en comentarios anteriores:
Map serializableMap = readJSON text: jsonText
fuente
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, "Salvajemente ineficaz, pero pequeño coeficiente: solución JSON slurp" (que estoy de acuerdo con este propósito). Los aspectos que necesitaba resolver:
java.io.NotSerializableException
problema, incluso cuando el texto JSON define contenedores anidados@NonCPS
)fuente
Noob error de mi parte. ¿Se movió el código de alguien de un antiguo complemento de canalización, jenkins 1.6? a un servidor que ejecute la última versión 2.x de jenkins.
Falló por esta razón: "java.io.NotSerializableException: groovy.lang.IntRange" Seguí leyendo y leyendo esta publicación varias veces por el error anterior. Realizado: para (num en 1..numSlaves) {IntRange - tipo de objeto no serializable.
Reescrito en forma simple: para (num = 1; num <= numSlaves; num ++)
Todo está bien en el mundo.
No uso Java o Groovy con mucha frecuencia.
Gracias chicos.
fuente
Encontré una forma más fácil de utilizar documentos externos para la canalización 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) }
fuente