Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XmlSlurper() in jenkins pipeline. how to avoid java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild

I'm trying to read properties from my pom.xml file. I tried the following and it worked:

steps {
    script {
        def xmlfile = readFile "pom.xml"
        def xml = new XmlSlurper().parseText(xmlfile)
        def version = "${xml.version}"
        echo version
    }
}

When I tried to do something like this:

steps {
    script {
        def xmlfile = readFile "pom.xml"
        def xml = new XmlSlurper().parseText(xmlfile)
        def version = "${xml.version}"
        def mystring = "blabhalbhab-${version}"
        echo mystring
    }
}

the pipeline suddenly fails with the error:

Caused: java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild

What might be the problem here?

EDIT: just adding this for others finding it with the same use case. My specific question was about how to avoid the CPS related error with XmlSlurper(). BUT for anyone else trying to parse POMs, afraid of that PR that supposedly will deprecate readMavenPom, the safest most maveny way of doing this is probably something like:

def version = sh script: "mvn help:evaluate -f 'pom.xml' -Dexpression=project.version -q -DforceStdout", returnStdout: true trim()

This way your using maven itself to tell you what the version is and not grepping or sedding all over the damn place. How to get Maven project version to the bash command line

like image 430
red888 Avatar asked Nov 08 '19 18:11

red888


1 Answers

In general, using groovy.util.slurpersupport.NodeChild (the type of your xml variable) or groovy.util.slurpersupport.NodeChildren (the type of xml.version) inside CPS pipeline is a bad idea. Both classes are not serializable, so you can't predicate their behavior in the Groovy CPS. For instance, I run successfully your second example in my Jenkins Pipeline. Most probably because the example you gave is not complete or something like that.

groovy:000> xml = new XmlSlurper().parseText("<tag></tag>")
===> 
groovy:000> xml instanceof Serializable
===> false
groovy:000> xml.tag instanceof Serializable
===> false
groovy:000> xml.dump()
===> <groovy.util.slurpersupport.NodeChild@0 node=groovy.util.slurpersupport.Node@5b1f29fa parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000> xml.tag.dump()
===> <groovy.util.slurpersupport.NodeChildren@0 size=-1 parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000> 

If you want to read pom.xml file, use the readMavenPom pipeline step. It is dedicated to read pom files and what is most important - it is safe to do it without applying any workarounds. This step comes with the pipeline-utility-steps plugin.

However, if you want to use XmlSlurper for some reason, you need to use it inside the method that is annotated with @NonCPS. That way you can access "pure" Groovy and avoid problems you have faced. (Yet still using readMavenPom is the safest way to achieve what you are trying to do.) The point here is to use any non-serializable objects inside a @NonCPS scope so the pipeline does not try to serialize it.

Below you can find a simple example of the pipeline that shows both approaches.

pipeline {
    agent any 

    stages {
        stage("Using readMavenPom") {
            steps {
                script {
                    def xmlfile = readMavenPom file: "pom.xml"
                    def version = xmlfile.version
                    echo "version = ${version}"
                }
            }
        }

        stage("Using XmlSlurper") {
            steps {
                script {
                    def xmlfile = readFile "pom.xml"
                    def version = extractFromXml(xmlfile) { xml -> xml.version }
                    echo "version = ${version}"
                }
            }
        }
    }
}

@NonCPS
String extractFromXml(String xml, Closure closure) {
    def node = new XmlSlurper().parseText(xml)
    return closure.call(node)?.text()
}

PS: not to mention that using XmlSlurper requires at least script 3 approvals before you can start using it.

like image 195
Szymon Stepniak Avatar answered Nov 19 '22 02:11

Szymon Stepniak