在 Jenkins 管道中解析 XML 文件

Parsing an XML file within a Jenkins pipeline

注:

我在我的 Jenkins 环境中只有一个主节点时回贴了这个问题。这使得序列化成为一个较小的问题,因为没有其他节点可以与之通信,因此这里的代码不会像多节点环境中预期的那样 运行 (例如, readFile 只会从主节点)。

根据我的经验,最好以其他格式(JSON、YAML、Groovy 编写您的配置,这些格式在 Jenkins 中都是本地支持的),或者使用外部工具(例如 xmllint on Linux) 如果您无法控制文件的格式。

原题:

我有一个 XML 文件,我想将其用作管道脚本的输入。问题是 XMLParser 不可序列化,所以我将它放在 NonCPS 函数中,但因此我丢失了 Node 对象。

这是管道脚本:

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
}

这是一个输入样本:

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

这是结果:

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[=13=]0(CpsThreadGroup.java:78)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.call(CpsThreadGroup.java:185)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.call(CpsThreadGroup.java:183)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.call(CpsVmExecutorService.java:47)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at hudson.remoting.SingleLaneExecutorService.run(SingleLaneExecutorService.java:112)
    at jenkins.util.ContextResettingExecutorService.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

我正在使用 Jenkins 2.7 和管道 2.1,这是目前最新的。

由于 branch 是根元素,因此在访问已解析的节点时无需显式指定它

尝试改变

println buildPlan.branch

println buildPlan.stage

打印阶段节点

A @NonCPS 方法应该只接受或 return Serializable 类型。尝试从方法中 returning .branch

最后我认为我的方法是错误的:我决定将 XML 文件转换成一个单独的 groovy 脚本并在管道中加载它

更新:最近人们开始为了清晰起见编辑我的答案,但事实是我只是放弃了将我的配置存储在 XML 文件中并选择了 groovy 脚本,这给了我更多的灵活性。我知道这可能不是一种常见的做法,但它适合我的需要。

例如 - 而不是:

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

我用过:

config.groovy:
[
  floopi: 2
]

并且在管道脚本中:

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

我希望这是一个更好的答案:)

您可以使用 XmlSlurper,它对我有用。

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

我可以推荐一个shared library。这将使您

源目录

class PipelineSupport {
    PipelineSupport(def env, def jenkinsStepAccess) {
        this.env = env
        this.jenkinsStepAccess = jenkinsStepAccess

    }
    def readXml(def path) {
        def text = jenkinsStepAccess.readFile(path)
        def parser = new XmlParser()
        def xml = parser.parseText(text.toString())
        return xml
    }
}

变量目录 var/foo.groovy

import ...PipelineSupport

Map readXml(def path) {
    // pass env and "this" access point to jenkins DSL to calls
    return new PipelineSupport(env, this).readXml(path)
}

在构建中使用

library "mylib"
def xmlData = foo.readXml('path/to/bar.xml')