helm: how to check if a service with prefix already exists in namespace - kubernetes

I want to implement some k8s services only if there is a service with suffix "mysuffix" in the namespace. I tried the following but doesn't work
{{- $previousReleaseInstalled := false -}}
{{- range $index, $service := (lookup "v1" "Service" .Release.Namespace "").items }}
{{- if hasSuffix "mysuffix" "$service.name" }}
{{- $previousReleaseInstalled := true -}}
{{- end }}
{{- end }}
{{- if $previousReleaseInstalled -}}
implement some services
{{- end }}

Your lookup function is almost correct. The issue with the assignment of the $previousReleaseInstalled variable. You need to use = instead of := inside the range. Check this answer for the details Why doesn't this change the value of the variable in the range loop in Helm?
Another issue is the wrong key name. You should use $service.metadata.name instead of $service.name, and remove the quotes around the $service.metadata.name
{{- $previousReleaseInstalled := false -}}
{{- range $index, $service := (lookup "v1" "Service" .Release.Namespace
"").items }}
{{- if hasSuffix "mysuffix" $service.metadata.name }}
{{- $previousReleaseInstalled = true -}}
{{- end }}
{{- end }}
{{- if $previousReleaseInstalled -}}
implement some services
{{- end }}
Another important thing to note, the lookup function always returns empty response when used with the helm template command. https://helm.sh/docs/chart_template_guide/function_list/#lookup

Related

helm using argument in template to check if value exists in Values.yaml

The problem is following: I want to check if field in Values.yaml exists based on argument given to the template in _helpers.tpl:
{{- define "example-name" -}}
{{- $objectRef := index . 0 -}}
{{- if .Values.custom -}}
{{- if .Values.custom.$objectRef -}}
{{- if .Values.custom.$objectRef.annotations -}}
{{- include "some-library" (tuple .Values.custom.$objectRef.annotations) | indent 4 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
Then in my deployment for example:
{{- template "example-name" "someField" }}
I want the result to be following:
{{- define "example-name" -}}
{{- $objectRef := index . 0 -}}
{{- if .Values.custom -}}
{{- if .Values.custom.someField-}}
{{- if .Values.custom.someField.annotations -}}
{{- include "some-library" (tuple .Values.custom.someField.annotations) | indent 4 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
But the only thing I get is following error: bad character U+0024 '$'
I want to use template multiple times with various arguments. I haven't seen anyone dealing with the same problem before.
Any ideas?
The standard template function you're looking for is index. In its simplest form, index $map $key does a dynamic lookup of the $key (can be any expression) in the provided $map (can be any expression). It can also do integer-index lookups in arrays (Go slices) and nested lookups if you need to.
The other problem that you'll run into is that $map.undefinedKey (or index $map "undefinedKey"), assuming undefinedKey is not present in $map, is valid but evaluates to Go nil. So you can't do further lookups in that. The workaround to this I typically use is to use the Helm (Sprig) default function to use an empty dict if a value is not present.
That would give you a template like:
{{- define "example-name" -}}
{{- $top := index . 0 -}}
{{- $objectRef := index . 1 -}}
{{- $custom := $top.Values.custom | default dict -}}
{{- $object := index $custom $objectRef | default dict -}}
{{- with $object.annotations -}}
{{- include "some-library" (list $top .) | indent 4 }}
{{- end }}
{{- end }}
This is called with a list of two values, the top-level Helm object and a reference to a key in .Values.custom
{{- include "example-name" (list . "someField") -}}
The template extracts the two values from the list parameter. It then traverses the values structure one level at a time, at each level defaulting to an empty dictionary. So for example if there is no .Values.custom then $custom is set to an empty dictionary, which allows index $custom $objectRef to execute successfully (and return nil, but not abort). At the bottom level we use the with template function to check to see if come value is truthy, and if so, temporarily bind . to its value. When we make the inner call, we already have the top-level Helm object in a variable, and we can assemble a list of $top and the non-empty annotation structure . as the single template parameter.

How to check for a non-existent dictionary value within a helm template?

In my values config file, I have an array of dictionaries as follows:
connects_to
- name: myname
release: optional release
- name: another name
Note that name will always be provided but release may or may not be. In my template, I have:
{{- if .Values.connects_to }}
app.openshift.io/connects-to: '
{{- range .Values.connects_to -}}
{{- if .release -}}
{{- .release -}}-{{- .name -}},
{{- else -}}
{{- $.Release.Name -}}-{{- .name -}},
{{- end -}}
{{- end -}}
'
{{- end }}
which gives the error:
can't evaluate field release in type interface {}
I have also tried using "hasKey" as follows:
{{- if .Values.connects_to }}
app.openshift.io/connects-to: '
{{- range .Values.connects_to -}}
{{- if hasKey . "release" -}}
{{- .release -}}-{{- .name -}},
{{- else -}}
{{- $.Release.Name -}}-{{- .name -}},
{{- end -}}
{{- end -}}
'
{{- end }}
which gives the error:
wrong type for value; expected map[string]interface {}; got string
How would I accomplish this check without having to specify a value for "release" every time? My helm version:
version.BuildInfo{Version:"v3.2.3+4.el8", GitCommit:"2160a65177049990d1b76efc67cb1a9fd21909b1", GitTreeState:"clean", GoVersion:"go1.13.4"}
This actually works with the "hasKey" approach, there was an error in the values definition.
For people looking for a possible cause for the error (wrong type for value; expected map[string]interface {}; got string).
Given the template
{{- if hasKey .Values.somedict "valueX" -}}
{{- .Values.somedict.valueX -}}
{{- end -}}
And using 2 values.yaml (eg: helm template . --values Values1.yaml --values Values2.yaml).
Values1.yaml:
somedict:
valueA: 1
Values2.yaml:
# Causes error
somedict:
# Works
somedict: {}
# Leaving out "somedict" also work
Regarding #DieterDP 's response https://stackoverflow.com/a/72360797/17143221
I am getting the same issue when using this template.
This can be solved by using parentheses around the optional dict as a mean of null-safety.
template:
# Fails when somedict doesn't exist
{{- if hasKey .Values.somedict "valueX" -}}
{{- .Values.somedict.valueX -}}
{{- end -}}
# Works when somedict doesn't exist
{{- if hasKey (.Values.somedict) "valueX" -}}
{{- .Values.somedict.valueX -}}
{{- end -}}
values.yaml:
# without "somedict"

If condition checking value returned by helm template

I have a parent chart with 2 subcharts. The parent chart has global.myflag while the subcharts have myflag fields, in their respective values.yaml. I want the flexibility, where the sub-charts could be deployed independently. So, I have added a template function in the sub-chart _helper.tpl where I want to check
- if global.myflag exists, use that value
- else use value of myflag from the subchart
The template will return true/false. Something like this -
{{- define "isFlagEnabled" -}}
{{- $flag := false -}}
{{- if .Values.myflag -}}
{{- $flag := .Values.myflag -}}
{{- end -}}
{{- if .Values.global.myflag -}}
{{- $flag := .Values.global.myflag -}}
{{- end -}}
{{- printf "%s" $flag -}}
{{- end -}}
And using this value (true/false), I want to set some values in my config.yaml.
{{- if eq (value from template) true -}}
I am having two questions here -
1. Can we do 'if' condition on the template values? How?
2. Is there a better way to do this?
Definition of isFlagEnabled template
Retouched and cleaned your function
{{- define "isFlagEnabled" -}}
{{- if .Values.global -}} {{/* <-- check parent exists to avoid nil pointer evaluating interface {}.myflag */}}
{{- if .Values.global.myflag -}}
{{- .Values.global.myflag -}}
{{- end -}}
{{- else if .Values.myflag -}} {{/* <-- make sure its else if so you wont override if both defined */}}
{{- .Values.myflag -}}
{{- else -}}
{{- printf "false" }}
{{- end -}}
{{- end -}}
Using the template
Inside another template
When using template inside golang template syntax, you will need to escape them with round brackets:
{{- define "flagUsage" -}}
{{- if eq (include "isFlagEnabled" .) "true" -}}
{{- printf "%s" (include "isFlagEnabled" .) -}}
{{- end -}}
{{- end -}}
another example used inside a resource
Pay attention the template is being used twice in the example, once as an operand for the if operator and one as text for the label
{{- if eq (include "isFlagEnabled" .) "true" -}} {{/* <--- operand used in spring function surrounded by `{{ }}` */}}
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-chart.fullname" . }}
labels:
my-meta-label: {{ include "isFlagEnabled" . }} {{/* <---- plain text */}}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "my-chart.selectorLabels" . | nindent 4 }}
{{- end }}
To build on #Totem's excellent and helpful answer, you can make the "isFlagEnabled" template reusable across multiple flags.
{{- define "isFlagEnabled" -}}
{{- $root := index . "root" -}}
{{- $flag := index . "flag" -}}
{{- if (index $root.Values $root.Chart.Name) -}}
{{- if hasKey (index $root.Values $root.Chart.Name) $flag -}}
{{- (index (index $root.Values $root.Chart.Name) $flag).enabled -}}
{{- end -}}
{{- else if hasKey $root.Values $flag -}}
{{- (index $root.Values $flag).enabled -}}
{{- else -}}
{{- printf "false" }}
{{- end -}}
{{- end -}}
Use it in the yaml file like so:
{{- if eq (include "isFlagEnabled" (dict "root" . "flag" "myflag")) "true" }}
Local values file:
myflag:
enabled: false
Overriding values file:
mychart:
myflag:
enabled: true
This worked for me in the situation where I had a subchart that needed to be overridden by a global chart, and I wanted to reference the chart by name in the global chart.

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"

Kubernetes Helm, combine two variables with a string in the middle

I’m trying to change the value of a variable if another variable it set by combining the two with a dash in the middle, I’m not sure of the syntax to do this, I’m thinking of somethings like:
{{- $serviceNamespace := .Values.serviceNamespace -}}
{{- $serviceTag := .Values.serviceTag -}}
{{- if $serviceTag}}
{{- $serviceNamespace := .Values.serviceNamespace "-" .Values.serviceTag -}}
{{- end}}
Is this correct? if serviceNamespace was hello and serviceTag was 1.0.0 would I end up with serviceNamespace being hello-1.0.0?
For concatenation just use printf:
{{- $serviceNamespace := printf "%s-%s" .Values.serviceNamespace .Values.serviceTag -}}
You can simply do it like this , with string ":" in middle
"{{ $values.image.repository }}:{{ $values.image.tag }}"
Update
It is now possible in the 1.11 version of golang, see commit:
{{- $serviceNamespace := .Values.serviceNamespace -}}
{{- $serviceTag := .Values.serviceTag -}}
{{- if $serviceTag}}
{{- $serviceNamespace = print .Values.serviceNamespace "-" .Values.serviceTag -}}
{{- end}}
Notice the new = operator in $serviceNamespace = print .Values.serviceNamespace "-" .Values.serviceTag
Older golang versions
You cannot currently (in golang 1.9, but available in 1.11, see update above) reassign template variables because if introduces a new scope. Until this is fixed (see issue and proposed fix), you can work around this by writing a function:
{{ define "makeServiceNamespace" }}
{{- if .Values.serviceTag }}
{{- printf "%s-%s" .Values.serviceNamespace .Values.serviceTag -}}
{{- else }}
{{- print .Values.serviceNamespace }}
{{- end }}
{{- end }}
Then use it like so:
serviceNamespace: {{ template makeServiceNamespace . }}