Detect if variable is dict / array / list - kubernetes-helm

I've the following values.yaml:
env:
NODES:
value:
- 192.168.178.1:1234
- 192.168.178.2:1234
- 192.168.178.3:1234
- 192.168.178.4:1234
PASSWORD:
valueFrom:
secretKeyRef:
name: foo
key: bar
Now, in the deployment I've the following lines to specify the environment:
env:
{{- range $name, $item := .Values.env }}
- name: {{ $name }}
{{- $item | toYaml | nindent 14 }}
{{- end }}
This works as long as "NODES" is no list because they are not allowed as env variable, but I would like to specify them as list in the values.yaml. So the question is, how I can test if $item.value is a dict or a simple string.
I tried using typeOf, but it tells me that NODES is "[]interface {}". So I wonder what's the correct way to test?!
My goal is, if I see an array in the env of my deployment to join them using ";", e.g. value: {{ join ";" .env.NODES.value | quote }}.

Helm has some template functions to inspect object types (from the Sprig support library) but these quickly get into the underlying Go type system.
For the specific thing you're asking, you might try the Helm/Sprig kindIs function. This passes through to the underlying Go reflect package but in particular it does distinguish slice (list) and map (dict) as specific kinds.
{{- if and $item.value (kindIs "slice" $item.value) }}
value: {{ join ";" $item.value }}
{{- end }}
Rather than trying to have your Helm values directly reflect the Kubernetes object structure, you might consider having configuration values like this list of nodes as top-level configuration values.
# values.yaml
# not inside an `env:` block, at the top level
nodes:
- 192.168.178.1:1234
- 192.168.178.2:1234
Then you can construct these from a known format in your template. This will be much more straightforward than trying to use the reflection functions.
env:
- name: NODES
value: {{ join ";" .Values.nodes }}

Related

helm --set to select environment

Struggling with some helm templating...
I'm trying to pass a separate yaml file with springboot parameters to helm, and have them split by environment... then I want to pass the environment to helm using --set env=staging
Feels like I've tried everything but clearly I'm lacking a fundamental understanding...
My _helpers.tpl contains these:
{{- define "env" }}
{{- printf "%s" .Values.env }}
{{- end }}
{{ define "configmap.metadata" }}
name: {{ .Values.name }}-config
{{ end }}
{{ define "configmap.properties" }}
{{ index .Values.environment (include "env" .) "properties" | indent 4 }}
{{ end }}
The template for the config map:
apiVersion: v1
kind: ConfigMap
metadata:
{{ include "configmap.metadata" . }}
data:
app.properties: |-
{{ include "configmap.properties" .}}
And the yaml file containing the properties looks like this:
environment:
staging:
properties:
spring:
datasource:
url: something
username: something
password: something
app1:
key: something
secret: something
baseUri: something
app2:
bootstrap_server: something
bootstrap_port: something
registry_schema: something
production:
properties:
spring:
etc, etc
And then I want to select the environment using set. I'm testing with:
helm template test . -f values.yaml -f properties.yaml --set env=staging
I think I've just tried so many things that I just can't see the wood for the trees! The error I'm seeing is:
Error: template: microservice/templates/configmap.yaml:7:7: executing "microservice/templates/configmap.yaml" at <include "configmap.properties" .>: error calling include: template: microservice/templates/_helpers.tpl:56:76: executing "configmap.properties" at <4>: wrong type for value; expected string; got map[string]interface {}
EDIT:
After tweaking, I'm still getting an error, but I'm seeing something in the configmap.. but I wonder if the error is due to the 8 spaces on the first line..
data:
app.properties: |-
app2:
bootstrap_port: something
bootstrap_server: something
registry_schema: something
app1:
baseUri: something
key: something
secret: something
spring:
datasource:
password: something
url: something
username: something
I think your actual error message is around the way you're using the .Values.environment.production.properties value. It's a YAML map, but the indent function expects it to be a string. You should be able to see some odd indentation and maybe an odd [map spring [map datasource ...]] string if you use the helm template --debug option.
When you go to render the ConfigMap, you need to make sure to do two things. Since the data you have is structured properties, you need to use the lightly-documented toYaml function to convert it back to YAML. This will begin at the first column, so you need to apply the indent function to it, and then you need to make sure the markup that invokes it is also at the first column (indent should be the only thing that supplies indentation).
data:
app.properties: |-
{{ include "configmap.properties" . | indent 4}}
{{/*- starts at column 1, but includes the `indent` function */}}
{{ define "configmap.properties" }}
{{- index .Values.environment (.Values.env) "properties" | toYaml }}
{{/*- starts at first column, includes `toYaml`, does not include `indent` */}}
{{- end }}

Helm template not able to read ip address - can't evaluate field ipAddress in type string

I am trying to create a helm template for Istio's ServiceEntry which has a list of addresses for static ip addresses. In values.yaml, I have
- name: test-se
namespace: test-se-ns
egressUrls:
- mydbhost.com
port: 32306
protocol: TCP
ipAddress: 10.2.2.2
In the .tpl file I am trying to add the value of ipAddress to a list
{{- with .ipAddress }}
addresses:
- {{ .ipAddress | quote }}
{{- end }}
Always fails with exception
templates/_service_entry.tpl:18:13: executing "common.serviceentry.tpl" at <.ipAddress>: can't evaluate field ipAddress in type string
Any idea what I am doing wrong?
If you use with you make the thing that you have used as with the context inside that block.
So, use the dot to refer to it.
{{- with .ipAddress }}
addresses:
- {{ . | quote }}
{{- end }}
From the docs:
{{with pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated;
otherwise, dot is set to the value of the pipeline and T1 is
executed.
In this case, an if seems also fitting, since you do not much with the new context.
{{- if .ipAddress }}
addresses:
- {{ .ipAddress | quote }}
{{- end }}
when you use with in Helm, you change the scope of the ., so Helm looks for an object and not a string, you can read more about it in the docs.
but anyway, I think that in your case, you need to use range instead of with, you can see an example here

Helm tpl function pass in file specific variable and global Values

Using the helm function tpl or other similar functions, how do you pass in a file specific variable and the top level Values? Here is a concrete example:
# values
template: "{{ .Values.name }} drinks {{ $drink }}"
name: "Tom"
# template
{{- $drink := "coffee" -}}
# how do I pass $drink into tpl???
{{ tpl .Values.template . }}
# expected output
Tom drinks coffee
It seems like when I do this it just passes in the .Values, but not the file specific $drink variable that's defined within the template and I get the error: error calling tpl. I don't see anything in the docs for how to merge these values together or just pass them both into the function.
Helm is using a slightly modified version of sprig functions. Most things from sprig are available.
You can use one of the dict functions to set the value or create a new dict that you pass as context.
{{ $_ := set .Values "drink" $drink }}
{{ tpl .Values.template . }}
In this case I have set a new key on the Values dict.
template: "{{ .Values.name }} drinks {{ .Values.drink }}"

Use variable to define other variables

Iā€™m trying to defines some variables in order to make my helm chart non-repeatable
I created a helpers file which contains the following section:
{{ $config := .Values.service }}
{{- range .Values.services }}
{{ $config.$serviceName }}
{{- define "{{ $serviceName }}.selectorLabels" -}}
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/instance: {{ .instance }}
{{- end -}}
{{- end -}}
values.yaml:
services:
- service1
- service2
- service3
service:
- service1:
name: service1
- service2:
name: service2
- service3:
name: service3
but it keeps prompting an error for bad character U+0024 ā€˜$ā€™
Do you know how can I define a variable by other variable?
Neither the Go text/template language, the Sprig extensions, nor Helm's local extensions have a way to define a template with a dynamic name. The name in a define call must be a fixed string. The text/template documentation notes (under "Nested template definitions"):
Template definitions must appear at the top level of the template.... The define action names the template being created by providing a string constant.
However, templates take a (single) parameter. Instead of trying to define a separate template for each dynamically-specified value, you could define a single template that produces this content, and then call it with the dynamic settings.
{{- define "selectorLabels" -}}{{/* <-- fixed name */-}}
{{/* .name is relative to the template parameter . */-}}
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/instance: {{ .instance }}
{{- end -}}
{{- range .Values.services }}
{{-/* . is one item from the services list */}}
{{ include "selectorLabels" . }}
{{- end -}}
You may find a simpler Helm values structure easier to work with as well. If you decompose .Values.service, it is a list, where each list is a single-item dictionary, where the key comes from a separate list. You might structure this as a single flat list of settings dictionaries, embedding the item name as a name: value within the structure (like for example the containers: list in a pod spec).
services:
- name: service1
instance: foo
- name: service2
instance: bar
- name: service3
instance: baz

How to reference a value defined in a template in a sub-chart in helm for kubernetes?

I'm starting to write helm charts for our services.
There are two things I'm not sure how they are supposed to work or what to do with them.
First: the release name. When installing a chart, you specify a name which helm uses to create a release. This release name is often referenced within a chart to properly isolate chart installs from each other? For example the postgres chart contains:
{{- define "postgresql.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
Which is then used for the service:
metadata:
name: {{ template "postgresql.fullname" . }}
It does look like "myrelease-postgresql" in the end in kubernetes.
I wonder what a good release name is? What is typically used for this? A version? Or some code-name like the ubuntu releases?
Second: referencing values.
My chart uses postgresql as a sub-chart. I'd like to not duplicate the way the value for the name of the postgresql service is created (see snipped above).
Is there a way I can reference the service name of a sub-chart or that template define {{ template "postgresql.fullname" . }} in the parent chart? I need it to pass it into my service as database host (which works if I hardcode everything but that cannot be the meaning of this).
I tried:
env:
- name: DB_HOST
value: {{ template "mychart.postgresql.fullname" . }}
But that lead into an error message:
template "mychart.postgresql.fullname" not defined
I've seen examples of Charts doing similar things, like the odoo chart. But in here that logic how the postgresql host name is created is copied and an own define in the template is created.
So is there a way to access sub-chart names? Or values or template defines?
Thanks!
Update after some digging:
According to Subcharts and Globals the templates are shared between charts.
So what I can do is this:
In my chart in _helpers.tpl I add (overwrite) the postgres block:
{{- define "postgresql.fullname" -}}
{{- $name := .Values.global.name -}}
{{- printf "%s-%s" $name "postgresql" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
So this value is used when the sub-chart is deployed. I cannot reference all values or the chart name in here as it will be different in the sub-chart - so I used a global value.
Like this I know the value of the service that is created in the sub-chart.
Not sure if this is the best way to do this :-/
Are you pulling in postgresql as a subchart of your chart (via your chart's requirements.yaml)? If so, both the postgresql (sub) chart and your chart will have the same .Release.Name - thus, you could specify your container's environment as
env:
- name: DB_HOST
value: {{ printf "%s-postgresql" .Release.Name }}
if you override postgresql's name by adding the following to your chart's values.yaml:
postgresql:
nameOverride: your-postgresql
then your container's env would be:
env:
- name: DB_HOST
value: {{ printf "%s-%s" .Release.Name .Values.postgresql.nameOverride }}
You can overwrite the values of the subchart with the values of the parent chart as described here:
https://helm.sh/docs/chart_template_guide/subcharts_and_globals/
I don't think it's possible (and it also doesn't make sense) to override the template name of the subchart.
What I would do is define the database service name in the .Values files both in the parent and sub charts and let helm override the one in the subchart - that way you will always have the database name in the parent chart. This would however mean that the service name of the database should not be {{ template "name" . }}, but something like {{ .Values.database.service.name }}
mychart/.Values
mysubchart:
service:
name: my-database
mychart/templates/deployment.yaml
env:
- name: DB_HOST
value: {{ .Values.mysubchart.service.name }}
mychart/charts/mysubchart/.Values
service:
name: my-database
mychart/charts/mysubchart/templates/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.service.name }}
Another way is to use global chart values, also described in https://helm.sh/docs/chart_template_guide/subcharts_and_globals/
For values in the helper.tpl instead of values.yaml
To access a value from a chart you do the following:
{{ template "keycloak.fullname" . }}
To access a value from a sub chart
{{ template "keycloak.fullname" .Subcharts.keycloak }}
You could import values from a sub chart as described here: https://helm.sh/docs/topics/charts/#importing-child-values-via-dependencies.
However there is a caveat. This works not for values defined at the root level in the values.yaml.
See this issue for more information: https://github.com/helm/helm/issues/9817