how do I loop over a nested map in helm charts? - kubernetes

I want to be able to pass in env vars plain text and from secrets like this:
envVars:
var1: 'sdfsdfsdf'
var2: 'efwefwefwef'
envVarsSecret:
var3:
secretKeyRef: my-secret
key: key
envVars works but envVarsSecret does not
env:
# This works
{{- range $key, $value := .Values.envVars }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
# This doesn't throw an error but when deployed secretKeyRef and key are blank (the secret was pre-created and exists)
{{- range $key, $value := .Values.envVarsSecret }}
- name: {{ $key }}
valueFrom:
secretKeyRef:
name: {{ $.Values.envVarsSecret.secretKeyRef | quote }}
key: {{ $.Values.envVarsSecret.key | quote }}
{{- end }

$. like that means you are trying to access a root value. You want something like {{ $value.secretKeyRef | quote }} and similar. Also that's not looping :)

Related

how to optimize this helm template if else

I need to reduce my 'if else code' in my helm chart template
How can I do that ?.
{{- if .Values.global }}
{{- if .Values.global.namespace }}
namespace: {{ .Values.global.namespace }}
{{- else }}
namespace: {{ .Values.namespace }}
{{- end }}
{{- else }}
namespace: {{ .Values.namespace }}
{{- end}}
name: {{.Values.name}}
You could use a variable and also {{with}} (which sets the dot), e.g.:
{{- $ns := .Values.namespace -}}
{{- with .Values.global }}{{ with.namespace }}{{ $ns = . }}{{end}{{ end -}}
namespace: {{ $ns }}
name: {{.Values.name}}
"If x is truthy, then use its value, otherwise use y" is what the Helm (Sprig) default function does. You could replace the inner conditional with
namespace: {{ .Values.global.namespace | default .Values.namespace }}
The outer conditional is trickier. The problem you're trying to work around here is, if .Values.global isn't defined, it will evaluate to nil, and then .Values.global.namespace is an error. The usual approach I use here is to again use default to get an empty dictionary if it isn't defined, at which point you can successfully do a lookup.
So you should be able to replace the entire block with
{{- $global := .Values.global | default dict }}
namespace: {{ $global.namespace | default .Values.namespace }}

Using Helm helper.tpl to set repository and image from the values.yaml or Chart.yaml

I a have a couple of charts for different products. In the first one a helper was written to build out the repo, image name and tag/version. This works but as the other Chart is quite different I've gone through a simpler approach but it does not work. I get the error,
error calling include: template: MYChart/templates/_helpers.tpl:94:28: executing "getImageName" at <.Values.registryName>: nil pointer evaluating interface {}.registryName
This is the helper.
{{/*
This allows us to not have image: .Values.xxxx.ssss/.Values.xxx.xxx:.Values.ssss
in every single template.
*/}}
{{- define "imageName" -}}
{{- $registryName := .Values.registryName -}}
{{- $imageName := .Values.imageName -}}
{{- $tag := .Chart.AppVersion -}}
{{- printf "%s/%s:%s" $registryName $imageName $tag -}}
{{- end -}}
These are the values
registry:
registryName: "index.docker.io/myrepo"
image_Name: "myimage"
Calling a value like the above in a _helper.tpl should work, there are plenty of examples that use this approach. What am I missing?
The template file :
{{- $root := . -}}
{{- $FullChartName := include "myapp.fullname" . -}}
{{- $ChartName := include "myapp.name" . -}}
{{- range $worker, $parameter := .Values.workerPods }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $parameter.name }}-worker
spec:
replicas: {{ $parameter.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $parameter.name }}-worker
app.kubernetes.io/instance: {{ $root.Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $parameter.name }}-worker
app.kubernetes.io/instance: {{ $root.Release.Name }}
autoscale: "true"
annotations:
{{- if $root.Values.worker.annotations }}
{{ toYaml $root.Values.worker.annotations | indent 8 }}
{{- end }}
spec:
imagePullSecrets:
- name: myapp-registry-credentials
containers:
- name: {{ $parameter.name }}-worker
image: {{ template "imageName" . }}
imagePullPolicy: {{ $root.Values.worker.image.pullPolicy }}
command: ["/bin/sh"]
args: ["-c", "until /usr/bin/pg_isready -h $DATABASE_HOST; do sleep 2; done; bundle exec rake jobs:work"]
{{- range $container, $containerResources := $root.Values.containers }}
{{- if eq $container $parameter.size }}
resources:
{{- toYaml $containerResources.resources | nindent 12 }}
{{- end }}
{{- end }}
envFrom:
- configMapRef:
name: common-env
- secretRef:
name: myapp-secrets
volumeMounts:
- name: mnt-data
mountPath: "/mnt/data"
volumes:
- name: mnt-data
persistentVolumeClaim:
claimName: myapp-pvc
{{- with $root.Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $root.Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $root.Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
I also tried this approach and added the following to the Chart.yaml but got a similar error, I'll be honest and wasn't sure this would even work but would be interested to hear other's thoughts.
annotations:
image: "myimage"
registry: "index.docker.io/myrepo"
And the helper looked like this.
{{/*
This allows us to not have image: .Values.xxxx.ssss/.Values.xxx.xxx:.Values.ssss
in every single template.
*/}}
{{- define "imageName" -}}
{{- $registryName := .Chart.Annotations.registry -}}
{{- $imageName := .Chart.Annotations.image -}}
{{- $tag := .Chart.AppVersion -}}
{{- printf "%s/%s:%s" $registryName $imageName $tag -}}
{{- end -}}
You're calling the template with the wrong parameter. Reducing the Helm template file to the bare minimum to demonstrate this:
{{- $root := . -}}
{{- range $worker, $parameter := .Values.workerPods }}
image: {{ template "imageName" . }}
imagePullPolicy: {{ $root.Values.worker.image.pullPolicy }}
{{- end }}
The standard Go text/template range statement rebinds the . variable (I believe to the same thing as $parameter). So then when you call the imageName template, its parameter isn't the Helm root value but rather the block from the values file; .Values is undefined and returns nil; and then .Values.registryName is a lookup on nil which produces the error you see.
One standard workaround to this is to save . to a variable outside the range loop and use that variable everywhere you would have used .. And in fact you already do this, the $root.Values.worker... reference in the following line should work correctly. You just need to change this at the point of call:
image: {{ template "imageName" $root }}

helm chart / go-template | Translate environment variables from string

I have a general helm chart in my Kubernetes cluster taking a multiline text field with environment variables (identified by KEY=VALUE), translating them into the deployment.yaml like this:
Inside the Rancher dialog:
In the deployment.yaml:
{{- if .Values.envAsMultiline }}
{{- range (split "\n" .Values.envAsMultiline) }}
- name: "{{ (split "=" .)._0 }}"
value: "{{ (split "=" .)._1 }}"
{{- end }}
{{- end }}
This works fine so far. But the problem now is: If I have a "=" in my environment variable (Like in the JAVA_OPTS above), it splits the environment variable value at the second "=" of the line:
JAVA_OPTS=-Xms1024m -Xmx2048m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512m
is translated to
-Xms1024m -Xmx2048m -XX:MetaspaceSize
The "=256M -XX:MaxMetaspaceSize=512m" is missing here.
How do I correct my deployment.yaml template accordingly?
Plan 1:
One of the simplest implementation methods
You can directly use the yaml file injection method, put the env part here as it is, so you can write the kv form value and the ref form value in the values in the required format.
As follows:
values.yaml
env:
- name: ENVIRONMENT1
value: "testABC"
- name: JAVA_OPTS
value: "-Xms1024m -Xmx2048m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M"
- name: TZ
value: "Europe/Berlin"
deployment.yaml
containers:
- name: {{ .Chart.Name }}
env:
{{ toYaml .Values.env | nindent xxx }}
(ps: xxx --> actual indent)
Plan 2:
Env is defined in the form of kv, which is rendered in an iterative manner
values.yaml
env:
ENVIRONMENT1: "testABC"
JAVA_OPTS: "-Xms1024m -Xmx2048m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M"
TZ: "Europe/Berlin"
deployment.yaml
containers:
- name: {{ .Chart.Name }}
env:
{{- range $k, $v := .Values.env }}
- name: {{ $k | quote }}
value: {{ $v | quote }}
{{- end }}
Plan 3:
If you still need to follow your previous writing, then you can do this
values.yaml
env: |
ENVIRONMENT1=testABC
JAVA_OPTS=-Xms1024m -Xmx2048m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M
TZ=Europe/Berlin
deployment.yaml
containers:
- name: {{ .Chart.Name }}
{{- if .Values.env }}
env:
{{- range (split "\n" .Values.env) }}
- name: {{ (split "=" .)._0 }}
value: {{ . | trimPrefix (split "=" .)._0 | trimPrefix "=" | quote }}
{{- end }}
{{- end }}
output:
env:
- name: ENVIRONMENT1
value: "testABC"
- name: JAVA_OPTS
value: "-Xms1024m -Xmx2048m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M"
- name: TZ
value: "Europe/Berlin"

helm - how to iterate over map with complex values

In a helm chart want to iterate over a map that contains structured values.
I do know how to iterate over a map with simple string values. I also can iterate over an array that contains structured values (not shown here). But I did not manage to iterate over a map that contains structured values.
This is my directory structure containing 3 files:
templates/test.yaml
Chart.yaml
values.yaml
A simple file Chart.yaml (just for completing the showcase):
---
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: foochart
version: 0.1.0
The file values.yaml with a map that contains simple string values (label) and one that contains structured values (label1):
---
label:
fook: foo
bark: bar
label1:
fook:
name: foo
value: foo1
bark:
name: bar
value: bar2
This template test.yaml works:
---
env:
{{- range $k, $v := .Values.label }}
- name: {{ $k }}
value: {{ $v }}
{{- end }}
But when I substitute .Values.label by .Values.label1, it produces no output.
This is my command for testing:
helm template foochart
Question: Is it possible to process a map with structured values? I would like to use something like $v.name. If yes, how can I do that?
You can in fact use syntax like $v.name, if you know that $v is a variable holding an object.
env:
{{- range $k, $v := .Values.label1 }}
- name: {{ $k }}_{{ $v.name }}
value: {{ $v.value }}
{{- end }}
If you know that it has exactly the syntax you want, there is an underdocumented toYaml function that takes an arbitrary object and returns it as unindented YAML. In your example, each of the values has the form of an env: item, and if you know (or specify) that, you can write out literally:
env:
{{- range .Values.label1 }}
- {{ . | toYaml | indent 4 | trim }}
{{- else }}
[]
{{- end }}
(In this last example: I'm not assigning a variable, so . is temporarily reassigned to each value in the map, and the keys are lost; for each item, I convert it to YAML, indent it by 4 spaces, but then trim out leading and trailing whitespace; and if there are no values, I explicitly write out an empty list.)
It's usually easier to specify a format you want your values to be in, and work with that. If you for some reason can't be sure which form you have, the template language includes functions to test on a value's type, so in principle you can test:
env:
{{- range $k, $v := .Values.labelN }}
{{- if kindIs "string" $v }}
- name: {{ $k }}
value: {{ $v }}
{{- else }}
- name: {{ $v.name }}
value: {{ $v.value }}
{{- end }}
{{- else }}
[]
{{- end }}

Passing dictionary from one template to another in Helm

I'm trying to pass a dictionary from one helm template to another but it's resolved to null inside the called template.
Calling template - deployment.yaml
Called template - storageNodeAffinity
I see myDict printed as map inside deployment.yaml but inside storageNodeAffinity it's printed as null.
Eventually I need to pass nodeAffn from the values file.
deployment.yaml
{{- $myDict := dict "cpu" "amd" }}
{{- include "storageNodeAffinity" $myDict | indent 6 }}
{{printf "%q" $myDict}}
storage-affinity.tpl
{{- define "storageNodeAffinity" }}
{{/* {{- $myDict := dict "cpu" "amd" }}*/}}
{{printf "%q" .myDict}}
{{- range $key, $val := .myDict }}
- key: {{ $key }}
operator: In
values:
- {{ $val }}
{{- end }}
{{- end }}
values.yaml
nodeAffn:
disktype: "ssd"
cpu: intel
When you call a template
{{- include "storageNodeAffinity" $myDict -}}
then within the template whatever you pass as the parameter becomes the special variable .. That is, . is the dictionary itself; you don't need to use a relative path to find its values.
{{- define "storageNodeAffinity" }}
{{/* ., not .myDict */}}
{{printf "%q" .}}
{{- range $key, $val := . }}...{{ end -}}
{{- end -}}
I figured it out. The trick is to pass context of the parent variable for the variable you want to use in the called template. So here I'm passing "csAffn" as context and then using "nodeAffn" inside this context, in the called template (_additionalNodeAffinity)
_additionalNodeAffinity.tpl
{{- define "additionalNodeAffinity" }}
{{- range $key, $val := .nodeAffn }}
- key: {{ $key }}
operator: In
values:
- {{ $val }}
{{- end }}
{{- end }}
deployment.yaml
{{- include "additionalNodeAffinity" ( .Values.csAffn )
values.yaml
csAffn:
nodeAffn:
disktype: "ssd"
cpu: "intel"