Groovy Yaml string interpolation with a list of maps

56 Views Asked by At

I am building a yaml string within Jenkins scripted pipeline that is parametrized. The problem is that I get error interpolating string with a variable that is a list of maps. I don't think I can use external libraries in the jenkins pipeline. There are some Jenkins plugins that are installed. Not sure which libs are available to serialize these objects properly into json or yaml. FWIW I am very inexperienced in groovy. I usually write 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)

ERROR:

com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot deserialize value of type `io.fabric8.kubernetes.api.model.VolumeMount`
from Array value (token `JsonToken.START_ARRAY`)
   at [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] (through reference chain: 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: An error has occurred.
      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: Failed to parse yaml:

I tried using literal strings but the code looks ugly. Hard to read and maintain.

1

There are 1 best solutions below

0
Dmitry Tokarev On BEST ANSWER

Solution is to convert Groovy objects to JSON and serialize JSON objects using built-in JSON library that is importable in Jenkins pipelines. Working with yaml strings was tricky and gave up on that. Here is the solution:

import groovy.json.JsonOutput

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: ${JsonOutput.toJson(volumeMounts)}
  volumes: ${JsonOutput.toJson(volumes)}
"""

println(yaml)