Search code examples
variableslist-comprehensionjsonnet

Would like to assign multiple variables from split() on one line of code


Given the following array:

// use strings only in the form <protocol>-<port>
ports: [
  'tcp-1514',
  'tcp-8080',
  'tcp-8443',
],

I'm trying to write jsonnet to split each element of the array to generate this object (represented here in yaml):

ports:
- name: "tcp-1514"
  containerPort: 1514
  protocol: "tcp"
- name: "tcp-8080"
  containerPort: 8080
  protocol: "tcp"
- name: "tcp-8443"
  containerPort: 8443
  protocol: "tcp"

I've tried several iterations of array comprehension to do this, mind you I'm brand new to jsonnet. The latest iteration was something like:

ports: [
  {
    local proto, port ::= std.split(port_obj, '-');
    name: port_obj,
    containerPort: port,
    protocol: proto,
  } for port_obj in $.sharedConfig.ports,
]

where $.sharedConfig.ports is the ports assignment. The problem is local proto, port ::= std.split(port_obj, '-');. I'm not sure this is valid code. The interpreter is poopooing it and I can't find any examples or documentation showing that this is valid.

Ultimately, if it's not valid then I'll have to split() twice, but that would be unfortunate. For instance, this works:

{
  local ports = ['tcp-1514', 'tcp-8080', 'tcp-8443',],

  ports: [
    local port = std.split(name,'-')[1];
    local proto = std.split(name,'-')[0];
  {

    name: name,
    protocol: proto,
    containerPort: port,
  }
  for name in ports],
}

which yields:

{
   "ports": [
      {
         "containerPort": "1514",
         "name": "tcp-1514",
         "protocol": "tcp"
      },
      {
         "containerPort": "8080",
         "name": "tcp-8080",
         "protocol": "tcp"
      },
      {
         "containerPort": "8443",
         "name": "tcp-8443",
         "protocol": "tcp"
      }
   ]
}

and YAML:

---
ports:
- containerPort: '1514'
  name: tcp-1514
  protocol: tcp
- containerPort: '8080'
  name: tcp-8080
  protocol: tcp
- containerPort: '8443'
  name: tcp-8443
  protocol: tcp

...but I really dislike the two-line variable assignment. The more I've tested this, the more I believe I'm right determining that the single-line assignment is not doable.

Anyone able to show me how I'm wrong, I'd truly appreciate it.


Solution

  • It may look like a simple answer (that you may have already considered), but well here it goes: using a single local var to hold the split() result, then refer to it in fields' assignments ->

    Simple answer:

    {
      local ports = ['tcp-1514', 'tcp-8080', 'tcp-8443'],
    
      ports: [
        local name_split = std.split(name, '-');
        {
    
          name: name,
          protocol: name_split[0],
          containerPort: name_split[1],
        }
        for name in ports
      ],
    }
    

    Obfuscated answer (no interim local w/split() result):

    // Return a map from zipping arr0 (keys) and arr1 (values)
    local zipArrays(arr0, arr1) = std.foldl(
      // Merge each (per-field) object into a single obj
      function(x, y) x + y,
      // create per-field object, e.g. { name: <name> },
      std.mapWithIndex(function(i, x) { [arr0[i]]: x }, arr1),
      {},
    );
    
    {
      local ports = ['tcp-1514', 'tcp-8080', 'tcp-8443'],
      // Carefully ordered set of fields to "match" against: [name] + std.split(...)
      local vars = ['name', 'protocol', 'containerPort'],
    
      ports: [
        zipArrays(vars, [name] + std.split(name, '-'))
        for name in ports
      ],
    }