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失败:
错误原因
- 直接插值Groovy列表会输出其默认字符串形式(如
[["mountPath:/build/toolchain", "name:volume-0"], ...]),不符合YAML语法,导致Kubernetes解析器无法识别为正确的VolumeMount对象。 - 代码存在笔误:
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
相关产品推荐
相关产品推荐

