Search code examples
yamlkubernetes-helm

Merge annotations in Helm


Assuming I have the following values.yaml for a subchart:

global:
  ingress:
    annotations: 
      nginx.ingress.kubernetes.io/configuration-snippet: |
        add_header yyy "yyy"; 

tag: 123
port: 1234
ingress: 
  enabled: true
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      add_header xxx "xxx"; 
...

how can I merge both annotation blocks in the ingress.yaml template together so that it results in:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations: |
    add_header xxx "xxx";
    add_header yyy "yyy";
...

Has anyone a hint where to start here?


Solution

  • Both values are just strings, and you can either write them one after another or concatenate them at the template layer.

    So, for example, a minimal thing that should come close to working, for this specific example, could be

    {{- $key := "nginx.ingress.kubernetes.io/configuration-snippet" -}}
    metadata:
      annotations: |
    {{ index .Values.ingress.annotations $key | trim | indent 4 }}
    {{ index .Values.global.ingress.annotations $key | trim | indent 4 }}
    

    This takes advantage of the fact that Helm doesn't really "understand" YAML at the templating layer; instead, a template writes out an arbitrary string and them Helm tries to parse it afterwards. So you can use the YAML | block scalar marker, and then write out arbitrary content under it, and so long as you get the indentation right it will work.


    The question sounds like it's reaching for a more general question of how to merge the two annotation lists, combining individual values by concatenating the strings. Helm includes a set of dictionary template functions, which somewhat unusually work by mutating a dictionary in-place. You can combine this with the underdocumented toYaml function to write the dictionary in valid YAML syntax.

    In pseudocode, I might write:

    1. Create a new dictionary that's a copy of the local annotations.
    2. Loop through the global annotations. For each, if the key does not exist, save its value, but if it does, append the global value.
    3. Write the result as YAML.

    You could translate this into Helm template code:

    {{- $annotations := deepCopy .Values.ingress.annotations -}}
    {{- range $key, $value := .Values.global.ingress.annotations -}}
      {{- $existing := get $annotations $key -}}
      {{- $new := cat $existing $value -}}
      {{- $_ := set $annotations $key $new -}}
    {{- end -}}
    metadata:
      annotations:
    {{ $annotations | toYaml | indent 4 }}
    

    In particular here I've taken advantage of the property that get returns an empty string if the key doesn't exist, which happens to be what you want in this particular case. For other "reduce values" type operations you might need to check if the value exists using hasKey, or use a default value.