Is it possible to define variables use if/else condition in helm chart? - kubernetes-helm

In my values.yaml I have:
isLocal: false
localEnv:
url: abcd
prodEnv:
url: defg
Then I have a service.yaml:
{{- if .Values.isLocal }}
{{- $env := .Values.localEnv }}
{{- else}}
{{- $env := .Values.prodEnv }}
{{- end}}
url: {{ $env.url}}
Unfortunately I got a 'undefined variable "$env"' error, does anyone have idea how to achieve this? Thanks!

One more way would be to create the variable before starting the if/else block. For example:
{{- $env := .Values.prodEnv -}}
{{ if .Values.isLocal }}
{{- $env = .Values.localEnv -}}
{{ end}}
Notice that I've not used the := operator inside the if block as the variable is already created.
and then
url: {{ $env.url}}

The Go text/template documentation notes:
A variable's scope extends to the "end" action of the control structure ("if", "with", or "range") in which it is declared....
so your declarations of $env go out of scope at the end of the {{ if }}...{{ end }} block, which isn't so useful.
Helm also includes (almost all of) a support template library called Sprig which includes a ternary function, which acts sort of like an inline "if" statement. For your example you could write
{{- $env := ternary .Values.localEnv .Values.prodEnv .Values.isLocal -}}
(Also consider just writing totally separate Helm values files for each environment, and installing your chart with helm install -f prod.yaml, instead of trying to encapsulate every possible environment in a single file.)

You actually can define the variable $env outside the if block and assign its value using = with if block.
{{- $env := "" }}
{{- if .Values.isLocal }}
{{- $env = .Values.localEnv }}
{{- else}}
{{- $env = .Values.prodEnv }}
{{- end}}
url: {{ $env.url}}
Please note: := will declare the variable while assigning the value. On the other hand, = will assign the value only.
If you use := in if, it will declare a new local variable $env which will not impact the value of $env declared outside the if block.

Related

How to concatenate variables inside a ternary statement?

I'm trying to do this:
name: {{ $value.enable | ternary $value.prefix $.Release.Namespace $value.suffix $value.override }}
But that syntax is wrong. I can't find any examples for how I would concatenate these vars together: $value.prefix $.Release.Namespace $value.suffix
Edit
I thought I could use print like this:
name: {{ true | ternary (print $value.prefix $.Release.Namespace $value.suffix) $value.fullnameOverride }}
But if you don't specify one of the fields it prints <nil> instead of not printing anything which is what I want.
Helm includes the Sprig template library which includes many useful composable parts.
For the "true" half, you have the three components; you'd like them joined together; but you'd like nil parts to be removed. The Sprig list function constructs a list from arbitrary items; compact takes a list and returns a new list without empty items (either empty string or nil, anything that's a Go zero value); and then join combines the list together into a single string. You can assign that to a temporary variable, and use it in the ternary call:
{{- $qualifiedName := list $value.prefix $.Release.Namespace $value.suffix | compact | join "" }}
name: {{ $value.enable | ternary $qualifiedName $value.fullnameOverride }}
I find the ternary syntax a little hard to read (even if it matches the C-style expr ? t : f syntax) and in this context it's not necessary. A helper template that spelled this out could be easier to understand later.
{{-/* Generate the name of the thing. Call with a list containing
the top-level Helm object and an item from the values file.
(Indented for readability, the template engine removes all of
the whitespace.) */-}}
{{- define "name" -}}
{{- $top := index . 0 -}}
{{- $value := index . 1 -}}
{{- if $value.enable -}}
{{- with $value.prefix -}}{{- . -}}{{- end -}}
{{- with $top.Release.Namespace -}}{{- . -}}{{- end -}}
{{- with $value.suffix -}}{{- . -}}{{- end -}}
{{- else -}}
{{- $value.fullnameOverride -}}
{{- end -}}
{{- end -}}
name: {{ include "name" (list $ .) }}

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 }}

Consul Template variable assinging with condition

I have to assign some variable depending on other variable and some conditions
I've tried to code this but with no luck
{{- $region_for_link := $region}}
{{- if eq $region "a"}} {{- $region_for_link := "AAA"}} {{- end}}
{{- if eq $region "b"}} {{- $region_for_link := "BBB"}} {{- end}}
I wanted to have value in $region_for_link AAA when $region is a, but after that $region_for_link is still a. How to code that properly, probably it is obvious case, but I can't get it.
Please help, I'm waiting for your answer
Best Regards
Consul-template uses a version of the Go Template engine. According to the documentation at https://golang.org/pkg/text/template/#hdr-Variables :
A pipeline inside an action may initialize a variable to capture the result. The initialization has syntax
$variable := pipeline
where $variable is the name of the variable. An action that declares a variable produces no output.
Variables previously declared can also be assigned, using the syntax
$variable = pipeline
The below code, resulted in printing of AAA:
{{ $region := "a" }}
{{- $region_for_link := $region}}
{{- if eq $region "a"}} {{- $region_for_link = "AAA"}} {{- end}}
{{- if eq $region "b"}} {{- $region_for_link = "BBB"}} {{- end}}
{{ $region_for_link }}

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.)

How to use .Values in other variable loops

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