Search code examples
kubernetesyamlkubernetes-helmhelm3

Iterate over a YAML complex map in helm


I am using Helm v3 and trying to iterate over a complex object/map in a YAML definition file for Kubernetes network policy with the following content:
values.yaml:

networkPolicies:
  egress:
    - service: microservice-name
      destination:
        - podLabels:
            app=microservice-name
          namespaceLabels:
             company.com/microservices: microservice-name
      protocol: TCP
      ports:
        - 8444

At the k8s definition file, I have this code:
egress-networkpolicy.yaml:

{{- range $v, $rule := .Values.networkPolicies.egress  }}---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-kong-to-{{ $rule.service }}-egress
  namespace: kong
  annotations:
    description: Kong egress policies
spec:
  podSelector:
    matchLabels:
      {{- range $label,$value := $rule.podSelector }}
      {{ $label }}: {{ $value }}
      {{- end }}
  egress:
    - to:
        {{- range $from := $rule.to }}
        - podSelector:
            matchLabels:
              {{- range $label,$value := $from.podLabels }}
              {{ $label }}: {{ $value }}
              {{- end }}
          {{- if has $from "namespaceLabels" }}
          namespaceSelector:
            {{- if eq ( len $from.namespaceLabels ) 0 }} {}
            {{- else }}
            matchLabels:
              {{- range $label,$value := $from.namespaceLabels }}
              {{ $label }}: {{ $value }}
              {{- end }}
            {{- end }}
          {{- end }}
        {{- end }}
      {{- if has $rule "ports" }}
      ports:
        {{- range $port := $rule.service.ports }}
        - protocol: {{ $port.protocol }}
        - port: {{ $port }}
        {{- end }}
      {{- end }}
    policyTypes:
      - Egress
{{ end -}}

Unfortunately when I run helm template name-of-the-template it throws the following error:

❯ helm template name-of-the-template --debug
install.go:178: [debug] Original chart version: ""ate name-of-the-template --debug  

install.go:199: [debug] CHART PATH: /Users/user/charts/name-of-the-template


Error: template: name-of-the-template/templates/egress-networkpolicy.yaml:34:13: executing "name-of-the-template/templates/egress-networkpolicy.yaml" at <has $rule "ports">: error calling has: Cannot find has on type string
helm.go:88: [debug] template: name-of-the-template/templates/egress-networkpolicy.yaml:34:13: executing "name-of-the-template/templates/egress-networkpolicy.yaml" at <has $rule "ports">: error calling has: Cannot find has on type string

I can't find the reason for Helm throwing this error at that line, but not in similar code before that line.


Solution

  • The has template function checks for membership in a list. In this context $rule is a mapping or dictionary (it is one of the items in the list under egress) and for that type you need to use hasKey instead.

    {{- if hasKey $rule "ports" }}{{/* hasKey, not has */}}
    ports:
      ...
    {{- end }}
    

    One thing that can simplify this slightly is to use the Go template with construct instead of if here. with acts just like if, except it also rebinds the special variable . to the conditional value if it's truthy. Accessing an undefined key in a map is okay but returns nil, which is falsey. So in this context I might write

    {{- with $rule.ports }}
    ports:
    {{- range . }}
      - protocol: {{ $rule.protocol }}
        port: {{ . }}
    {{- end }}
    {{- end }}
    

    (But note as I've written it that the . in the third and fifth lines are different variables, and that this usage also changes . as appears at the start of .Values or in template "name" . In this little loop that's not going to be a practical problem.)