How to use .Values in other variable loops - kubernetes-helm

Below is my case:
{{- $v := (.Files.Get "values-productpage.yaml") | fromYaml }}.
spec:
{{- range $key, $value := $v.containers }}
containers:
- name: {{ $value.name }}
image: {{.Values.productpage_image}}:latest
Here when reaching .Values.productpage_image, it reports: can't evaluate field productpage_image in type interface {}.
Is there any usage error here? Why can I not use .Values.xxx in this loop? If I move the .Values to the first line, there is no error.

You can simply use $ to get to the root scope
Without defining what $root is, you can references .Values as $.Values from within a loop, or any other scope.
Source: https://github.com/kubeapps/kubeapps/pull/1057

It's because range changes a scope (see detailed description here https://github.com/helm/helm/blob/master/docs/chart_template_guide/control_structures.md#looping-with-the-range-action).
You can assign .Values.productpage_image to the variable outside the range and use inside.

As #abinet explained properly about the reason, I'll share my solution for that( which helped me a lot, and I hope that will save you time):
First, I saved the scope:
{{- $root := . -}}
and after that , I called the .Value inside the loop context like this:
{{ $root.Values.data }}
so basically , you code should be look like:
{{- $root := . -}}
{{- $v := (.Files.Get "values-productpage.yaml") | fromYaml }}.
spec:
{{- range $key, $value := $v.containers }}
containers:
- name: {{ $value.name }}
image: {{$root.Values.productpage_image}}:latest

Related

Helm - only create if nested values are set

I am wondering if there is a more efficient way to exclude any yaml keys which do not have a value set.
My current approach is to wrap each key in an if statement...
container:
spec:
{{- if values.spec.x }}
x: {{ values.spec.x }}
{{- end}}
{{- if values.spec.y }}
y: {{ values.spec.y }}
{{- end}}
{{- if values.spec.z }}
z: {{ values.spec.z }}
{{- end}}
e.g.
for each child of container.spec:
if the value != null:
include as child of spec
else:
exclude from spec
I thought about wrapping the above in a _helper.tpl function to try to keep the main template tidy, but it would still include writing multiple if statements.
Is there a better way of doing the above?
Thanks!
You can directly translate that pseudocode into Helm chart logic. The trick is that a Go template range loop is basically equivalent to a "for" loop in most languages. So:
container:
spec:
{{- range $key, $value := .Values.spec }}
{{- if ne $value nil }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
If you can just omit the unused keys from the values, then this becomes simpler and safer. Helm includes a lightly-documented toYaml function that will render an arbitrary structure as YAML, but you can't really do any filtering or other preprocessing before writing it out.
container:
spec:
{{ .Values.spec | toYaml | indent 4 }}

How can I apply template function to a range result in Helm?

My goal is to convert values in Values.yaml into the following:
CUSTOM_VARIABLE: "TEST_ENV_1=devil,TEST_ENV_2=god,TEST_ENV_3=angel"
### Values.yaml
env:
TEST_ENV_1: devil
TEST_ENV_2: god
TEST_ENV_3: angel
The below template almost does this but I'm getting comma at the end: TEST_ENV_1=devil,TEST_ENV_2=god,TEST_ENV_3=angel,.
### _envVars.tpl
{{ define "envVars" }}
...
- name: CUSTOM_VARIABLE
value: "
{{- range $key, $value := .Values.env -}}
{{- printf "%s=%s," $key $value -}}
{{- end -}}
"
...
{{- end }}
Is there a way to apply template function (e.g. trunc to remove last symbol) to a range result in my case?
try something like
{{range $i, $e := $}}
{{if $i}},{{end}}
{{$e}}{{end}}
If actually look for the index and if it's zero it's wont to add the , at last. here is if is not behave like normal it checks the index also.
{{- range $i, $e := . -}}
{{if $i}}, {{end}}prefix_{{$e}}
{{- end}}
above loop will give output like : prefix_one, prefix_two, prefix_three
https://play.golang.org/p/KuRh55BHna8
Read more at : https://groups.google.com/g/golang-nuts/c/XBScetK-guk/m/Bh7ZFz6R3wQJ
If you write the range call into a helper template, Helm has an include extension function that calls a template and captures its output as a string.
{{/* Render the map-type template parameter to a key=value,key=value,
list, ending with a trailing comma. */}}
{{- define "custom.variable.format" -}}
{{- range $key, $value := . -}}
{{ $key }}={{ $value }},
{{- end -}}
{{- end -}}
- name: CUSTOM_VARIABLE
value: {{ include "custom.variable.format" .Values.env | trimSuffix "," | quote }}
(It is probably cleaner to not generate the comma at all, as #HarshManvar's answer proposes.)

loop through files of helm chart and inject values

I have a directory structure like this:
helm
|-->mappings
|--> foo
foo1.yaml foo2.yaml
|-->templates
mapping.yaml
values.yaml
where values.yaml
has a value that I need to be a variable due to environment like {{ .Values.data.hostname }}
and in mapping.yaml
{{- $files := .Files }}
{{- range .Values.mappings.foo }}
{{- $genericfilepath := printf "mappings/foo/%s.yaml" . }}
{{ $files.Get $genericfilepath }}
{{- end }}
Currently the mapping.yaml file loops through the designated directory and load the yaml file however I am unable to access the Values variable.
I have also attempted subchart where values.yaml file would be under helm/mappings/foo/values.yaml but it also doesn't resolve or I am not 100% understanding if subchart would be the correct solution to resolve the file path
In the Go text/template language, . is a special "context" variable, and references like .Files or .Values are actually retrieving fields from .. For example, you could write a sample template:
{{- $dot := . -}}
# These both print the same value
dot-values-foo: {{ .Values.foo }}
dollars-dot-values-foo: {{ $dot.Values.foo }}
One of the ways . is special is that the range statement sets . to each item as it iterates through a collection. In your example:
{{/* . is the top item; .Values is valid */}}
{{- range .Values.mappings.foo }}
{{/* . is one of the items in `mappings.foo` */}}
{{- end }}
{{/* . is the top item again */}}
If I need to use . for some special purpose like this, I tend to save the original top item in a variable, and then I can refer to fields in that.
{{- $top := . }}
{{- range .Values.mappings.foo }}
{{- $genericfilepath := printf "mappings/foo/%s.yaml" . }}
{{ $top.Files.Get $genericfilepath }}
{{ index $top.Values.enabled . }}
{{- 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"

Adding news lines when defining collection

I am trying to define a collection (dict), and I would like to add a new line on each definition (for readability), Eg:
{{ $deployment := dict
"Release" .Release
"Chart" .Chart
"Values" .Values }}
But when I do this, helm respond a parse error :
Error: parse error in "XXX": template: XXX:2: unclosed action
Error: UPGRADE FAILED: parse error in "XXX": template: XXX:2: unclosed action
Is there a way in HELM to do this?
I achieved this by defining the dict first and then setting one key per line.
{{- $myDict := dict "" "" -}}
{{- $_ := set $myDict "myKey1" "myValue1" -}}
{{- $_ := set $myDict "myKey2" "myValue2" -}}
{{- $_ := set $myDict "myKey3" "myValue3" -}}
{{- $_ := set $myDict "myKey4" "myValue4" -}}
Bonus Tip: Since dict get function is available seemingly in only helm3 and later, you can use this hack to get a value from a dict to a string.
{{/* Hack needed until helm 3 which has 'get' for 'dict' */}}
{{- $myValue3Var := pluck "myKey3" $myDict | first -}}
TLDR;
It's impossible to declare dict in multiline way, like with Perl fat comma operator.
Please check the reference of "Sprig: Template functions for Go templates."
Instead you could use this sort of hacky way to achieve similar result:
Keep each key value pair in separate line, in Global Values file for readability:
# values.yaml
--
global:
someMap:
coffee: robusta
origin: Angola
crema: yes
Define helper template in _helpers.tpl:
{{- define "mychart.labels.standard"}}
{{- $global := default (dict) .Values.global.someMap -}}
Release: {{ .Release.Name | quote }}
Chart: {{ .Chart.Name }}
Values:
{{- $global := default (dict) .Values.global.someMap -}}
{{- range $key, $value := $global }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end -}}
Include it in another template:
helm_data:
{{- $global := default (dict) .Values.global -}}
{{- range $key, $value := $global }}
{{ $key }}: {{ $value }}
{{- end }}
{{ include "mychart.labels.standard" . | nindent 0 -}}
Render it to verify the result (helm template --name dict-chart .)
---
# Source: mychart/templates/data_type.yaml
helm_data:
someMap: map[crema:true origin:Angola coffee:robusta]
Release: "dict-chart"
Chart: mychart
Values:
coffee: robusta
crema: true
origin: Angol
It seems it's impossible to do so. The Helm templating system is basically the Go templating system. As stated in the Go templating docs:
Except for raw strings, actions may not span newlines, although comments can.
For people coming across this question, this functionality works in recent versions of HELM. For me, OPs example works as-is (Helm v3.8.2).
(I came across this question myself due to a mismatched ) in my template.)