kubernetesjsonnet

Tanka/Jsonnet - Howto loop over templates/imports?


i am trying to create multiple grafana-instances with slightly different config-files with tanka. The following works, as long as the the configmap.grafana_ini is in-place. But this becomes very unreadable with a growing config. So i am looking for a way to move the configmaps to their own file and import.

But if i move that to it's own file and use an import/str i am getting a "computed imports are not allowed" error or the instance-variable becomes unknown.

local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';
local helm = tanka.helm.new(std.thisFile);

local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';


(import 'config.libsonnet') +

{
  local configMap = k.core.v1.configMap,
  local container = k.core.v1.container,
  local stateful = k.apps.v1.statefulSet,
  local ingrdatasourcesess = k.networking.v1.ingress,
  local port = k.core.v1.containerPort,
  local service = k.core.v1.service,
  local pvc = k.core.v1.persistentVolumeClaim,

  local ports = [port.new('http', 3000)],

  grafana: {
    g(instance):: {

      local this = self,

      deployment:
        stateful.new(
          name='grafana-' + instance.handle,
          replicas=1,
          containers=[
            container.new(
              name='grafana-' + instance.handle,
              image=$._config.grafana.image + instance.theme + ':' + $._config.grafana.version
            )
            + container.withPorts(ports),
          ],
        )
        + stateful.metadata.withLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
        + stateful.configMapVolumeMount(this.configMaps.grafana_ini, '/etc/grafana/grafana.ini', k.core.v1.volumeMount.withSubPath('grafana.ini'))
        + stateful.spec.withServiceName('grafana-' + instance.handle)
        + stateful.spec.selector.withMatchLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
        + stateful.spec.template.metadata.withLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
        + stateful.spec.template.spec.withImagePullSecrets({
          name: 'registry.gitlab.com',
        })
        + stateful.spec.template.spec.withRestartPolicy('Always'),    

      service:
        k.util.serviceFor(self.deployment)
        + service.mixin.spec.withType('ClusterIP'),

      configMaps: {
        grafana_ini:
          configMap.new(
            'grafana-ini-' + instance.handle, {
              'grafana.ini': std.manifestIni(
                {
                  main: {
                    app_mode: 'production',
                    instance_name: instance.handle,
                  },
                  sections: {
                    server: {
                      protocol: 'http',
                      http_port: '3000',
                      domain: 'dashboard.' + $._config.ingress.realm + '.' + $._config.ingress.tld + '/' + instance.handle + '/',
                      root_url: $._config.ingress.protocol + 'dashboard.' + $._config.ingress.realm + '.' + $._config.ingress.tld + '/' + instance.handle + '/',
                      serve_from_sub_path: true,
                    },
                
                  },
                }
              ),
            }
          ),
      },
    },
    deploys: [self.g(instance) for instance in $._config.grafana.instances],
  },
}

Here is the config-part:

{
  _config+:: {
    grafana+: {
      image: 'registry.gitlab.com/xxx/frontend/grafana/',
      version: 'v7.3.7',
      client_secret: 'xyz',
      adminusername: 'admin',
      adminpassword: 'admin',
      instances: [
        {
          name: "xxx's Grafana",
          handle: 'xyz',
          theme: 'xxx',
          alerting: 'false',
          volume_size: '200M',
          default: true,
          allow_embedding: false,
          public: 'false',
          secret_key: 'xxxx',
          email: {
            host: '',
            user: '',
            password: '',
            from_address: '',
            from_name: '',
          },
          datasources: [
            {
              name: 'xxx Showcase',
              type: 'influxdb',
              access: 'proxy',
              url: 'http://influx:8086',
              database: 'test123',
              user: 'admin',
              password: 'admin',
              editable: false,
              isDefault: false,
              version: 1              
            },
          ],
          dashboards: [
            {
              src: 'provisioning/dashboards/xxx_showcase_dashboard.json',
              datasource: 'xxx Showcase',
              title: 'xxx office building',
              template: true,
            },
          ],
        },
      ],
    },
  },
}

EDITED, as suggested by 2nd post.

greetings, strowi


Solution

  • Thanks for clarifying, find below a possible solution, note I trimmed down parts of your original files, for better readability.

    I think that the main highlight here is the iniFile() function, so that we can explicitly pass (config, instance) to it.

    main.jsonnet

    local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';
    local helm = tanka.helm.new(std.thisFile);
    
    local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';
    
    
    (import 'config.libsonnet') +
    
    {
      local configMap = k.core.v1.configMap,
      local container = k.core.v1.container,
      local stateful = k.apps.v1.statefulSet,
      local ingrdatasourcesess = k.networking.v1.ingress,
      local port = k.core.v1.containerPort,
      local service = k.core.v1.service,
      local pvc = k.core.v1.persistentVolumeClaim,
    
      local ports = [port.new('http', 3000)],
    
      grafana: {
        g(instance):: {
    
          local this = self,
    
          /* <snip...> */
          configMaps: {
            local inilib = import 'ini.libsonnet',
            grafana_ini:
              configMap.new(
                'grafana-ini-' + instance.handle, inilib.iniFile($._config, instance)
              ),
          },
        },
        deploys: [self.g(instance) for instance in $._config.grafana.instances],
      },
    }
    

    config.libsonnet

    {
      _config+:: {
        // NB: added below dummy ingress field
        ingress:: {
          realm:: 'bar',
          tld:: 'foo.tld',
          protocol:: 'tcp',
        },
        grafana+: {
          image: 'registry.gitlab.com/xxx/frontend/grafana/',
          version: 'v7.3.7',
          client_secret: 'xyz',
          adminusername: 'admin',
          adminpassword: 'admin',
          instances: [
            {
              name: "xxx's Grafana",
              handle: 'xyz',
              /* <snip...> */
            },
          ],
        },
      },
    }
    

    ini.libsonnet

    {
      iniFile(config, instance):: {
        'grafana.ini': std.manifestIni(
          {
            main: {
              app_mode: 'production',
              instance_name: instance.handle,
            },
            sections: {
              server: {
                protocol: 'http',
                http_port: '3000',
                domain: 'dashboard.' + config.ingress.realm + '.' + config.ingress.tld + '/' + instance.handle + '/',
                root_url: config.ingress.protocol + 'dashboard.' + config.ingress.realm + '.' + config.ingress.tld + '/' + instance.handle + '/',
                serve_from_sub_path: true,
              },
    
            },
          }
        ),
      },
    }