kubernetesjsonnetksonnet

Jsonnet - Apply mixin multiple times in for loop


I am using Jsonnet to render Kubernetes objects, I would like to use a mixin to apply multiple volume mounts to a deployment.

An example of what I've tried (k being the kausal library):

config: {
  mountConfig: [
    first: {
      mountPath: '/path/to/first',
      subPath: 'first',
      data: {},
    },
    second: {
      mountPath: '/path/to/second',
      subPath: 'second',
      data: {},
    },
  ],
}

configMap: configMap.new('config')
           + configMap.withData(
               { [config.mountPath]: config.data }
               for config in $.mountConfig
             ),

deployment: deployment.new(
              name='dep1',
              replicas='2',
              containers=[container.new('name', 'image')]
            )
            + k.util.configMapVolumeMount($.configMap, config.mountPath, config.subPath)
              for mount in $.config.mountConfig

The above creates a single configmap with a key for each config file and then attempts to mount the configmap multiple times for each config file defined within the configmap.

Obviously an incomplete example, I can't figure out the syntax to do a + mixin for something in somethings.


Solution

  • Afaicf subPath handling against a single configMap is more involved in kausal.libsonnet libs,as k.util.configMapVolumeMount() calls will end up adding the configmap entry to pod's volumes[] many times.

    I think that the below snippet solves your question, mind that there are also other syntax fixes (besides the implementation mechanics).

    source: foo.jsonnet

    // foo.jsonnet
    local k = import 'jsonnet-libs/ksonnet-util/kausal.libsonnet',
          deployment = k.apps.v1.deployment,
          container = k.core.v1.container,
          configMap = k.core.v1.configMap,
          volumeMount = k.core.v1.volumeMount;
    
    local config = {
      mountConfig: [
        {
          mountPath: '/path/to/first',
          subPath: 'first',
          data: 'Data ONE',  // NOTE: must be a string
        },
        {
          mountPath: '/path/to/second',
          subPath: 'second',
          data: 'Data TWO',  // NOTE: must be a string
        },
      ],
    };
    
    {
      configMap:
        configMap.new('config')
        + configMap.withData({
          [e.subPath]: e.data
          for e in config.mountConfig
        }),
    
      deployment:
        local configVolName = $.configMap.metadata.name + '-volume';
    
        deployment.new(
          name='dep1',
          replicas=2,
          containers=[
            container.new('name', 'image')
            + container.withVolumeMountsMixin([
              volumeMount.new(configVolName, e.mountPath)
              + volumeMount.withSubPath(e.subPath)
              for e in config.mountConfig
            ]),
    
          ]
        )
        + deployment.spec.template.spec.withVolumesMixin([
          k.core.v1.volume.fromConfigMap(configVolName, $.configMap.metadata.name),
        ]),
    
    }
    

    shell run and output

    ## Initialize jsonnet-bundler
    $ jb init
    
    ## Add jsonnet libs
    $ jb install https://github.com/grafana/jsonnet-libs
    GET https://github.com/grafana/jsonnet-libs/archive/5f8a166911d56d0402fd52cbbce47cadfee0e466.tar.gz 200
    
    ## Add Kube 1.27 underlying libs, see https://tanka.dev/known-issues
    $ jb install github.com/jsonnet-libs/k8s-libsonnet/1.27@main
    GET https://github.com/jsonnet-libs/k8s-libsonnet/archive/6ecbb7709baf27f44b2e48f3529741ae6754ae6a.tar.gz 200
    $ mkdir -p lib; echo "import 'github.com/jsonnet-libs/k8s-libsonnet/1.27/main.libsonnet'" > lib/k.libsonnet
    
    ## Spit-out cm+deployment JSON, need to massage it a bit for `kubectl` to slurp it in
    $ jsonnet -J lib -J vendor foo.jsonnet |jq -S '.[]' |kubectl apply --dry-run=server -f-
    configmap/config created (server dry run)
    deployment.apps/dep1 created (server dry run)
    

    rendered YAML from manifested JSON

    The output from jsonnet -J lib -J vendor foo.jsonnet| yq -y .

    configMap:
      apiVersion: v1
      data:
        first: Data ONE
        second: Data TWO
      kind: ConfigMap
      metadata:
        name: config
    deployment:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: dep1
      spec:
        minReadySeconds: 10
        replicas: 2
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            name: dep1
        template:
          metadata:
            labels:
              name: dep1
          spec:
            containers:
              - image: image
                imagePullPolicy: IfNotPresent
                name: name
                volumeMounts:
                  - mountPath: /path/to/first
                    name: config-volume
                    subPath: first
                  - mountPath: /path/to/second
                    name: config-volume
                    subPath: second
            volumes:
              - configMap:
                  name: config
                name: config-volume