Jon O
Jon O

Reputation: 6591

Go template: is there any item in list of objects with a specific attribute value?

I'm using helm (sprig, go templates). I'm trying to build guards to selectively include stuff in my helm chart, but only if one of the components needs them.

So, I have a list:

- name: foo
  flag1: true
  flag2: false
  flag3: false
- name: bar
  flag1: false
  flag2: true
  flag3: false

I want to do something akin to a (pseudocode) list.any(flag), where over a variable length list, if I passed in flag1 or flag2 I'd get back true, but flag3 would get me false. If possible, I'd like to be able to ask about a different flag without repeating myself each time.

Is there a concise way to accomplish this? Can it be done?

Upvotes: 0

Views: 2696

Answers (1)

David Maze
David Maze

Reputation: 159403

The set of things that are and aren't possible in Go templates can be a little mysterious. A named template always returns a string, but an empty string is logically "false", so it should be possible to write a template call like

{{- if (include "list.any" (list .Values.options "flag2")) }}
...
{{- end }}

A template only takes a single parameter, so in the call we've packed the multiple inputs we need into a list. We've also used the Helm-specific include function to invoke a template and get its output as a string.

How can the template work? Template range loops don't have break or return actions or any other way to stop early. If we only want to output the "success" value once, this means we need to manually iterate through the list. For reasonably short lists, a recursive template call works here.

(For this specific thing, outputting yes or yesyesyes would both be non-empty and therefore logically "true", so you could use a range loop here successfully. This would not work for an equivalent list.all, though.)

In the template definition

{{- define "list.any" -}}
...
{{- end -}}

we need to start by unpacking the parameter list

{{- $list := index . 0 -}}
{{- $search := index . 1 -}}

We only do something if the list is non-empty.

{{- if $list -}}
...
{{- end -}}

If it is non-empty, we can split out its first element. We expect that to be a map, so we can look up the requested key in that with the standard index function. This will return nil if the key is absent and false if it's false, both of which are logically false; if it's true then the if test will pass.

{{- if index (first $list) $search -}}
...
{{- else -}}
...
{{- end -}}

If we do find the item, we can write out a success value and not do anything else

yes

If we don't, then we can recursively call ourselves with the remainder of the list.

{{- include "list.any" (list (rest $list) $search) -}}

Combining that all together gives this template (indented for clarity, the - markers will consume all of the whitespace):

{{- define "list.any" -}}
  {{- $list := index . 0 -}}
  {{- $search := index . 1 -}}
  {{- if index (first $list) $search -}}
    yes
  {{- else -}}
    {{- include "list.any" (list (rest $list) $search) -}}
  {{- end -}}
{{- end -}}

Upvotes: 2

Related Questions