You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkins脚本化流水线Groovy YAML列表映射插值报错求助

Jenkins脚本化流水线参数化YAML插值错误解决

问题描述

在Jenkins脚本化流水线中构建参数化YAML字符串时,插值列表映射类型变量出现解析错误。无法使用外部库,对已安装插件中可用于序列化对象的工具不明确。Groovy经验有限,日常以Python开发为主。尝试过字面字符串实现,但代码可读性与可维护性极差。

原始代码

def yaml, volumeMounts

volumeMounts = [
    ['mountPath': '/build/toolchain',      'name': 'volume-0'],
    ['mountPath': '/build/apps',           'name': 'volume-1'],
    ['mountPath': '/horizonci/automation', 'name': 'volume-2'],
    ['mountPath': '/home/jenkins',         'name': 'workspace-volume']
]

volumes = [
    ['name': 'volume-0', 'nfs': ['path': '/toolchain',           'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-1', 'nfs': ['path': '/apps',                'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-2', 'nfs': ['path': '/ifs/standard/devops', 'readOnly': true, 'server': 'my.server.company.com']]
]

yaml = """
kind: Pod
spec:
  containers:
  - volumeMounts: ${volumeMounts}
  volumes: ${volumesYml.toString()}
"""

println(yaml)

错误信息

com.fasterxml.jackson.databind.exc.MismatchedInputException:
无法将数组值(token `JsonToken.START_ARRAY`)反序列化为`io.fabric8.kubernetes.api.model.VolumeMount`类型
   位于 [Source: (String)"{"apiVersion":"v1","kind":"Pod","spec":{"containers":[{"name":"app","image":"jenkins/agent:latest","volumeMounts":[["mountPath:/build/toolchain","name:volume-0"],
        ["mountPath:/build/apps","name:vol"[truncated 1028 chars]; line: 1, column: 420] (引用链: io.fabric8.kubernetes.api.model.Pod["spec"]->io.fabric8.kubernetes.api.model.PodSpec["containers"]->java.util.ArrayList[0]->io.fabric8.kubernetes.api.model.Container["volumeMounts"]->java.util.ArrayList[0])

      at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
      at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1601)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
      at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
      at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
      at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
      at io.fabric8.kubernetes.client.utils.Serialization.unmarshalJsonStr(Serialization.java:311)
      at io.fabric8.kubernetes.client.utils.Serialization.unmarshalYaml(Serialization.java:306)
      at io.fabric8.kubernetes.client.utils.Serialization.unmarshal(Serialization.java:245)
 Caused: io.fabric8.kubernetes.client.KubernetesClientException: 发生错误。
      at io.fabric8.kubernetes.client.KubernetesClientException.launderThrowable(KubernetesClientException.java:64)
      at io.fabric8.kubernetes.client.KubernetesClientException.launderThrowable(KubernetesClientException.java:53)
      at io.fabric8.kubernetes.client.utils.Serialization.unmarshal(Serialization.java:249)
      at io.fabric8.kubernetes.client.dsl.base.OperationSupport.unmarshal(OperationSupport.java:656)
      at io.fabric8.kubernetes.client.dsl.base.BaseOperation.load(BaseOperation.java:315)
      at io.fabric8.kubernetes.client.dsl.base.BaseOperation.load(BaseOperation.java:86)
      at org.csanchez.jenkins.plugins.kubernetes.PodTemplateUtils.parseFromYaml(PodTemplateUtils.java:566)
      at org.csanchez.jenkins.plugins.kubernetes.PodTemplateUtils.validateYamlContainerNames(PodTemplateUtils.java:595)
      at org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateStepExecution.start(PodTemplateStepExecution.java:142)
      at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:319)
      at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:193)
      at org.jenkinsci.plugins.workflow.cps.CpsScript.invokeMethod(CpsScript.java:122)
      at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:48)
      at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
      at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
      at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:20)
 Caused: java.lang.RuntimeException: 解析yaml失败:

错误原因

  1. 直接插值Groovy列表会输出其默认字符串形式(如[["mountPath:/build/toolchain", "name:volume-0"], ...]),不符合YAML语法,导致Kubernetes解析器无法识别为正确的VolumeMount对象。
  2. 代码存在笔误:volumesYml.toString()应为volumes.toString(),但即使修正,仍会因格式问题报错。

解决方案

方案1:利用Jenkins内置Jackson库序列化(推荐)

Jenkins环境中已包含Jackson库(从报错信息可见Kubernetes客户端依赖它),可直接用其将Groovy对象序列化为标准YAML格式。

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory

// 初始化YAML序列化器
def yamlMapper = new ObjectMapper(new YAMLFactory())

def volumeMounts = [
    ['mountPath': '/build/toolchain',      'name': 'volume-0'],
    ['mountPath': '/build/apps',           'name': 'volume-1'],
    ['mountPath': '/horizonci/automation', 'name': 'volume-2'],
    ['mountPath': '/home/jenkins',         'name': 'workspace-volume']
]

def volumes = [
    ['name': 'volume-0', 'nfs': ['path': '/toolchain',           'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-1', 'nfs': ['path': '/apps',                'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-2', 'nfs': ['path': '/ifs/standard/devops', 'readOnly': true, 'server': 'my.server.company.com']]
]

// 序列化为YAML并移除开头的---标记
def volumeMountsStr = yamlMapper.writeValueAsString(volumeMounts).replaceFirst(/^---\n/, '').stripIndent()
def volumesStr = yamlMapper.writeValueAsString(volumes).replaceFirst(/^---\n/, '').stripIndent()

// 拼接最终YAML
def yaml = """
kind: Pod
spec:
  containers:
  - volumeMounts: ${volumeMountsStr}
  volumes: ${volumesStr}
"""

println(yaml)

方案2:手动构建YAML片段

若不想依赖Jackson,可通过Groovy的集合操作手动生成符合YAML语法的字符串,适合简单结构场景。

def volumeMounts = [
    ['mountPath': '/build/toolchain',      'name': 'volume-0'],
    ['mountPath': '/build/apps',           'name': 'volume-1'],
    ['mountPath': '/horizonci/automation', 'name': 'volume-2'],
    ['mountPath': '/home/jenkins',         'name': 'workspace-volume']
]

def volumes = [
    ['name': 'volume-0', 'nfs': ['path': '/toolchain',           'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-1', 'nfs': ['path': '/apps',                'readOnly': true, 'server': 'tools.company.com']],
    ['name': 'volume-2', 'nfs': ['path': '/ifs/standard/devops', 'readOnly': true, 'server': 'my.server.company.com']]
]

// 构建volumeMounts的YAML片段
def volumeMountsYaml = volumeMounts.collect { mount ->
    "  - mountPath: ${mount.mountPath}\n    name: ${mount.name}"
}.join('\n')

// 构建volumes的YAML片段(处理布尔值和字符串的格式)
def volumesYaml = volumes.collect { vol ->
    def nfsContent = vol.nfs.collect { key, value ->
        def formattedVal = value instanceof Boolean ? value : "\"${value}\""
        "      ${key}: ${formattedVal}"
    }.join('\n')
    "  - name: ${vol.name}\n    nfs:\n${nfsContent}"
}.join('\n')

// 拼接最终YAML
def yaml = """
kind: Pod
spec:
  containers:
  - volumeMounts:
${volumeMountsYaml}
  volumes:
${volumesYaml}
"""

println(yaml)

说明

  • 方案1利用环境已有的库生成标准YAML,避免手动拼接的错误,可读性和可维护性更好。
  • 方案2无需依赖额外工具,适合对YAML格式熟悉的场景,灵活度高。

内容的提问来源于stack exchange,提问作者Dmitry Tokarev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 12:48:10