Parsing an XML file within a Jenkins pipeline

2020-06-16 08:18发布

问题:

I have an XML file which I'd like to use as input for a pipeline script. Problem is the XMLParser isn't serializable so I put it in a NonCPS function, but I lost the Node object because of that.

This is the pipeline script:

def buildPlanPath = 'C:\\buildPlan_test.xml'

@NonCPS
groovy.util.Node getBuildPlan(path) {
    new XmlParser().parseText(readFile(path))
}

node {
    //def buildPlan = new XmlParser().parseText(readFile(buildPlanPath))
    groovy.util.Node buildPlan = getBuildPlan(buildPlanPath)

    println buildPlan.getClass()
    println buildPlan
    println buildPlan.branch
}

This is an input sample:

<branch name='mybranch'>
    <stage>
        <job name='job11' />
        <job name='job12' />
    </stage>
    <stage>
        <job name='job21' />
        <job name='job22' />
        <job name='job23' />
    </stage>
    <stage>
        <job name='job31' />
    </stage>
</branch>

This is the result:

Started by user admin
[Pipeline] node
Running on master in C:\Jenkins\workspace\pipeline-develop
[Pipeline] {
[Pipeline] readFile
[Pipeline] echo
class java.lang.String
[Pipeline] echo
<branch name='mybranch'>
  <stage>
    <job name='job11' />
    <job name='job12' />
  </stage>
  <stage>
    <job name='job21' />
    <job name='job22' />
    <job name='job23' />
  </stage>
  <stage>
    <job name='job31' />
  </stage>
</branch>
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
groovy.lang.MissingPropertyException: No such property: branch for class: java.lang.String
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:53)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:458)
    at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getProperty(DefaultInvoker.java:25)
    at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:17)
    at WorkflowScript.run(WorkflowScript:16)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:62)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:54)
    at sun.reflect.GeneratedMethodAccessor327.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:58)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:276)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$000(CpsThreadGroup.java:78)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:185)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:183)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Finished: FAILURE

I'm using Jenkins 2.7 with pipeline 2.1, which are currently the latest.

回答1:

You could use XmlSlurper, it works for me.

def xmlText = new XmlSlurper().parse(MyURL)
xmlText.data.artifact.each {******


回答2:

As branch is the root element, you don't need to explicitly specify it when accessing your parsed nodes

Try changing

println buildPlan.branch

To

println buildPlan.stage

To print out the stage nodes



回答3:

A @NonCPS method should only accept or return Serializable types. Try returning .branch from the method.



回答4:

In the end I think my approach was wrong: I decided to convert the XML file into a separate groovy script and load it within the pipeline

Update: Recently people started editing my answer for clarity, but the fact is that I just ditched storing my configuration in XML files and opted for groovy scripts, which gave me more flexibility. I understand it may not be a common practice, but it suits my needs.

For example - instead of:

config.xml:
<settings>
  <floopi>2</floopi>
</settings>

I used:

config.groovy:
def call() {[
  floopi: 2
]}
return this

And in the pipeline script:

stage('init') {
    def settings = load('config.groovy')()
    echo "floopi: ${settings.floopi}"
}

I hope that's a better answer :)