Using Helm templates, I'm trying to generate a list of server names based on a number in values.yaml. The dot for this template is set to the number (its a float64).
{{- define "zkservers" -}}
{{- $zkservers := list -}}
{{- range int . | until -}}
{{- $zkservers := print "zk-" . ".zookeeper" | append $zkservers -}}
{{- end -}}
{{- join "," $zkservers -}}
{{- end -}}
For an input of, say, 3 I'm expecting this to produce:
zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper
It produces nothing.
I understand that the line within the range block is a no-op since the variable $zkservers is a new variable each time the loop iterates. It is not the same variable as the $zkservers in the outer scope.
I hope the intention is clear of what I want to do. I am at a loss how to do it.
Anyone know how to do this with Helm templates?
For those from 2020+ it now can be achieved as simple as that:
{{ join "," .Values.some.array }}
It produces the correct output:
value: "test1,test2,test3"
At least it works with (more or less) recent versions of helm-cli:
Client: &version.Version{SemVer:"v2.16.2", GitCommit:"bbdfe5e7803a12bbdf97e94cd847859890cf4050", GitTreeState:"clean"}
Another quick way of doing it:
{{- define "helm-toolkit.utils.joinListWithComma" -}}
{{- $local := dict "first" true -}}
{{- range $k, $v := . -}}{{- if not $local.first -}},{{- end -}}{{- $v -}}{{- $_ := set $local "first" false -}}{{- end -}}
{{- end -}}
If you give this input like:
test:
- foo
- bar
And call with:
{{ include "helm-toolkit.utils.joinListWithComma" .Values.test }}
You'll get the following rendered:
foo,bar
This is from OpenStack-Helm's Helm-toolkit chart, which is a collection of utilities for similar purposes.
I faced with the same problem and your solution with dictionary saved my day. It's a good workaround and it can be just a little simpler:
{{- define "zkservers" -}}
{{- $zk := dict "servers" (list) -}}
{{- range int . | until -}}
{{- $noop := printf "zk-%d.zookeeper" . | append $zk.servers | set $zk "servers" -}}
{{- end -}}
{{- join "," $zk.servers -}}
{{- end -}}
You're currently defining a new varialbe in the scope of the list while you want to alter the existing value.
In order to fix your bug, you only need to change the way you assign the value to $zkservers:
- {{- $zkservers := print "zk-" . ".zookeeper" | append $zkservers -}}
+ {{- $zkservers = print "zk-" . ".zookeeper" | append $zkservers -}}
which gives you the following script:
{{- define "zkservers" -}}
{{- $zkservers := list -}}
{{- range int . | until -}}
{{- $zkservers = print "zk-" . ".zookeeper" | append $zkservers -}}
{{- end -}}
{{- join "," $zkservers -}}
{{- end -}}
With this slight modification, {{ include "zkservers" 3 }}gives you the output zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper
I was doing the same but on elasticsearch helm chart. Please find my example
values.yml:
...
replicaCount: 3
...
StatefulSets.yaml:
...
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.registry_address }}/{{ .Values.image.name }}:{{ .Chart.AppVersion }}"
env:
- name: ELASTICSEARCH_CLUSTER_NAME
value: "{{ .Values.env.ELASTICSEARCH_CLUSTER_NAME }}"
- name: ELASTICSEARCH_DISCOVERY_ZEN_PING_UNICAST_HOSTS
value: "{{ template "nodes" .Values }}"
...
_helpers.tpl:
{{/* vim: set filetype=mustache: */}}
{{- define "nodes" -}}
{{- $nodeCount := .replicaCount | int }}
{{- range $index0 := until $nodeCount -}}
{{- $index1 := $index0 | add1 -}}
elasticsearch-{{ $index0 }}.elasticsearch{{ if ne $index1 $nodeCount }},{{ end }}
{{- end -}}
{{- end -}}
This produces a comma separated list:
...
- name: ELASTICSEARCH_DISCOVERY_ZEN_PING_UNICAST_HOSTS
value: "elasticsearch-0.elasticsearch,elasticsearch-1.elasticsearch,elasticsearch-2.elasticsearch"
...
I got it working using:
{{- define "zkservers" -}}
{{- $dot := dict "nodes" (int .) "servers" (list) -}}
{{- template "genservers" $dot -}}
{{- join "," $dot.servers -}}
{{- end -}}
{{- define "genservers" -}}
{{- range until .nodes -}}
{{- $noop := print "zk-" . ".zookeeper" | append $.servers | set $ "servers" -}}
{{- end -}}
{{- end -}}
Seems a little bit verbose for what should normally be a simple one/two liner :)
How about just rendering and then trimming?
{{- include "template" . | trimSuffix "," -}}
Related
I have the function:
{{- define "myapp.getSubKey" -}}
{{- $map := .source }}
{{ "Before: " }}{{ $map }}
{{- range $key, $value := .keys }}
{{- if kindIs "int" $value }}
{{- $map := index $map (int $value) }}
{{ "After: " }}{{ $map }}
{{- end }}
{{- end }}
{{ $map }}
{{- end }}
I call it with include "myapp.getSubKey" (dict "source" .Values.vars "keys" list(0))
This prints out:
Before: [map[name:MYSQL_ROOT_PASSWORD valueFrom:map[secretKeyRef:map[key:db-pass name:db-creds]]] map[name:MYSQL_ROOT_USER valueFrom:map[secretKeyRef:map[key:db-user name:db-creds]]]]
After: [map[name:MYSQL_ROOT_PASSWORD valueFrom:map[secretKeyRef:map[key:db-pass name:db-creds]]]
[map[name:MYSQL_ROOT_PASSWORD valueFrom:map[secretKeyRef:map[key:db-pass name:db-creds]]] map[name:MYSQL_ROOT_USER valueFrom:map[secretKeyRef:map[key:db-user name:db-creds]]]]
So you can see it correctly navigates down and changes $map within the if statement, but when it exits the loop, it goes back to what it was before the loop.
How do I change the "global" value?
Try using the operator = instead of assignment operator := in the inner if-block. When you assigned the $map with :=, the scope is limited only to the if-block.
{{- define "myapp.getSubKey" -}}
{{- $map := .source }}
{{ "Before: " }}{{ $map }}
{{- range $key, $value := .keys }}
{{- if kindIs "int" $value }}
{{- $map = index $map (int $value) }} // <------- here
{{ "After: " }}{{ $map }}
{{- end }}
{{- end }}
{{ $map }}
{{- end }}
The operator = is supported since helm v2.13.0.
I'm trying to provide idiomatic access to a value defined in a template to a named template being included. This much works:
# values.yml
versions:
- 1.2.3.4
- 5.6.7.8
# _helpers.tmpl
{{/*
Get the service path from the version string (passed as ".")
*/}}
{{- define "sample.servicePath" -}}
{{- $pat := "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+$" }}
{{- $version := . | trimAll "\"" }}
{{- if regexMatch $pat $version }}
{{- regexReplaceAll $pat $version "${1}" }}
{{- else }}
{{- printf "Can't extract service number from version string %s" . | fail -}}
{{- end }}
{{- end }}
# sample.yml
{{- range $version := .Values.versions }}
{{- $servicePath := (include "sample.servicePath" $version) }}
# Here is the servicePath: {{ $servicePath }} which works
{{- end }}
But I imagined that this would also work:
# _helpers.tmpl (further down in the file)
{{- define "sample.labels" -}}
servicePath: {{ $servicePath }}
{{- end }}
# sample.yml
{{- range $version := .Values.versions }}
{{- $servicePath := (include "sample.servicePath" $version) }}
# Here is the servicePath: {{ $servicePath }} which still works
# but these labels: {{ include "sample.labels" . }} do not
{{- end }}
Ultimately I don't mind the implementation, I just need to be able to take a single list of values, range over them (to create multiple resources), and parse their value.
The Go text/template language, and by extension Helm charts, doesn't have global variables or dynamic scopes; you can't do this in the way you're suggesting. Each template invocation has its own scope, and the only things it can see are its single parameter . and any variables it declares locally.
However, Helm includes the Sprig extension library, which includes list and dict functions to create composite values. You can use these to pass multiple values into a template:
{{- define "sample.labels" -}}
{{- $top := index . 0 -}}
{{- $servicePath := index . 1 -}}
app.kubernetes.io/instance: {{ $top.Release.Name }}
servicePath: {{ $servicePath }}
{{ end -}}
{{- range $version := .Values.versions }}
{{- $servicePath := (include "sample.servicePath" $version) }}
{{- include "sample.labels" (list . $servicePath) }}
In the last line, we are invoking the template with a list of two parameters, the top-level Helm object (here as .) and the $servicePath local variable. The top of the sample.labels template then unpacks that back into two local variables within its own template, and then uses those to generate a reference set of labels.
I have been asked to modify a Helm template to accommodate a few changes to check if a value is empty or not as in the code snippet below. I need to check $var.alias inside the printf in the code snippet and write custom logic to print a custom value. Any pointers around the same would be great.
{{- range $key, $value := .Values.testsConfig.keyVaults -}}
{{- range $secret, $var := $value.secrets -}}
{{- if nil $var.alias}}
{{- end -}}
{{ $args = append $args (printf "%s=/mnt/secrets/%s/%s" $var.alias $key $var.name | quote) }}
{{- end -}}
{{- end -}}
I decided to test what madniel wrote in his comment. Here are my files:
values.yaml
someString: abcdef
emptyString: ""
# nilString:
templates/test.yaml
{{ printf "someEmptyString=%q)" .Values.someString }}
{{ printf "emptyString=%q)" .Values.emptyString }}
{{ printf "nilString=%q)" .Values.nilString }}
{{- if .Values.someString }}
{{ printf "someString evaluates to true" }}
{{- end -}}
{{- if .Values.emptyString }}
{{ printf "emptyString evaluates to true" }}
{{- end -}}
{{- if .Values.nilString }}
{{ printf "nilString evaluates to true" }}
{{- end -}}
{{- if not .Values.emptyString }}
{{ printf "not emptyString evaluates to true" }}
{{- end -}}
{{- if not .Values.nilString }}
{{ printf "not nilString evaluates to true" }}
{{- end -}}
Helm template output:
➜ helm template . --debug
install.go:173: [debug] Original chart version: ""
install.go:190: [debug] CHART PATH: <REDACTED>
---
# Source: asd/templates/test.yaml
someEmptyString="abcdef")
emptyString="")
nilString=%!q(<nil>))
someString evaluates to true
not emptyString evaluates to true
not nilString evaluates to true
So yes, it should work if you use {{ if $var.alias }}
I've below parameters in values.yaml. I need to fetch the value of the schema but the database name may change
preparedDatabases:
a3s:
schemas:
a3s:
I'm using below functions in tpl to return the databasename and the schema name.
{{/* Return Databasename
*/}}
{{- define "databasename" }}
{{- range $key, $val := .Values.spec.preparedDatabases -}}
{{- $key | toYaml | trim }}
{{- end -}}
{{- end -}}
{{/* Return Schemaname
*/}}
{{- define "schema" }}
{{- $dbname := include "databasename" . }}
{{- range $key, $val := ( .Values.spec.preparedDatabases.{{ $dbname }}.schemas) -}}
{{- $key | toYaml | trim }}
{{- end -}}
{{- end -}}
But it returns the error bad character U+0024 '$'
Could you please help on how can i interpret the variable dbname to be used with .Values
Thanks in advance
I'm building a Helm 3 library chart and would like to convert an input value to another value that's based on a preset map.
Say my values.yaml includes the following value:
global:
environment: production # (production/staging/test/development)
I have the following convention for converting a long environment name to a short one:
production => prod
staging => stage
test => test
development => dev
I would like to use the value stored in .Values.global.environment to generate a deployment name that is prefixed with the short environment name. In this case, it should be mapped to
prod-<application_name>.
How can this be achieved with Helm 3?
Add a helper function under templates/_helpers.tpl file.
Unfortunately, there's no switch functionality in go templates so it has to be "dirty" if else
{{/*
Environment name mapping
*/}}
{{- define "my-chart.environment" -}}
{{- if .Values.global.environment -}}
{{- if eq .Values.global.environment "production" -}}
{{- printf "prod" -}}
{{- else if eq .Values.global.environment "staging" -}}
{{- printf "stage" -}}
{{- else if eq .Values.global.environment "test" -}}
{{- printf "test" -}}
{{- else if eq .Values.global.environment "development" -}}
{{- printf "dev" -}}
{{- end -}}
{{- end -}}
{{- end -}}
then, use this template in the my-chart.fullname template
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "my-chart.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
##### ADDITIONAL LINES
{{- if .Values.global.environment -}}
{{- printf "%s-%s-%s" (include "my-chart.environment" .) .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
##### END ADDITIONAL LINES
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
Test:
/apps/my-chart # helm version
version.BuildInfo{Version:"v3.1.2", GitCommit:"d878d4d45863e42fd5cff6743294a11d28a9abce", GitTreeState:"clean", GoVersion:"go1.13.8"}
/apps/my-chart # cat ./values.yaml
# Default values for my-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
global:
environment: development
Running helm template .
output:
...
...
# Source: my-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-RELEASE-NAME-my-chart
...
...