Using include inside range in Go templates (helm) - kubernetes-helm

I have a template that get rendered several times with a range iteration and I can access variables external variables such as $.Release.Name without a problem. However, when I include templates I can't get it to work:
{{ range $key, $val := $.Values.resources }}
...
annotations:
checksum/config: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
{{ end }}
And in secrets.yaml:
apiVersion: "v1"
kind: "Secret"
metadata:
name: {{ $.Release.Name }}-secrets
I got this error:
Error: render error in "botfront-project/templates/deployment.yaml": template: [filename] :19:28: executing [filename] at <include (print $.Template.BasePath "/secrets.yaml") .>: error calling include: template: .../secrets.yaml:4:19: executing ".../secrets.yaml" at <$.Release.Name>: nil pointer evaluating interface {}.Name
How do I access variables inside an included template?

TL;DR;
just replace . with $ to use the global scope instead of the local one you created .
Example:
{{- include "my-chart.labels" $ | nindent 4 }}
Explanations
According to the docs, https://helm.sh/docs/chart_template_guide/control_structures/#modifying-scope-using-with:
we can use $ for accessing the object Release.Name from the parent
scope.
$ is mapped to the root scope when template execution begins
and it does not change during template execution
With range we change the scope inside the loop. Indeed, {{- include "my-chart.labels" . | nindent 4 }} would invoke the current scope ..
So if you dig into this "scope" thing in helm doc, you eventually find this part: https://helm.sh/docs/chart_template_guide/variables/
With this example:
{{- range .Values.tlsSecrets }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .name }}
labels:
# Many helm templates would use `.` below, but that will not work,
# however `$` will work here
app.kubernetes.io/name: {{ template "fullname" $ }}
# I cannot reference .Chart.Name, but I can do $.Chart.Name
helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
app.kubernetes.io/instance: "{{ $.Release.Name }}"
# Value from appVersion in Chart.yaml
app.kubernetes.io/version: "{{ $.Chart.AppVersion }}"
app.kubernetes.io/managed-by: "{{ $.Release.Service }}"
type: kubernetes.io/tls
data:
tls.crt: {{ .certificate }}
tls.key: {{ .key }}
---
{{- end }}

Related

Create kubernetes docker-registry secret from yaml file for each lookup namespaces?

I am trying to dynamic lookup available namespaces and able to create secrets in the namespaces using below helm chart.
templates/secrets.yaml
{{ range $index, $namespace := (lookup "v1" "Namespace" "" "").items }}
apiVersion: v1
kind: Secret
metadata:
name: myregcred
namespace: {{ $namespace.metadata.name }}
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ template "imagePullSecret" . }}
{{- end}}
values.yaml
imageCredentials:
registry: quay.io
username: someone
password: sillyness
email: someone#host.com
_helpers.tpl
{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}
When i run this helm chart, i get below error
Error: INSTALLATION FAILED: template: secrets/templates/_helpers.tpl:2:16: executing "imagePullSecret" at <.Values.imageCredentials>: nil pointer evaluating interface {}.imageCredentials
I dont know what I am doing wrong here.
When you reference the named template "imagePullSecret" inside the range, the context "." you are providing refers to the body of the loop, which does not have the "Values" attribute.
Try providing the root context instead:
{{ range $index, $namespace := (lookup "v1" "Namespace" "" "").items }}
apiVersion: v1
kind: Secret
metadata:
name: myregcred
namespace: {{ $namespace.metadata.name }}
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ template "imagePullSecret" $ }}
---
{{- end}}

How do I pass a map as a Helm tpl string?

I'm trying to populate the annotations on a service which is templated like this:
apiVersion: v1
kind: Service
metadata:
name: example
namespace: {{ .Release.Namespace }}
annotations:
{{- if .Values.service.annotations }}
{{ tpl .Values.service.annotations . | nindent 4 | trim }}
{{- end }}
(rest omitted for brevity.)
I tried to populate the service.annotations like this in my values.yaml:
service:
annotations:
foo: my-foo
bar: 6
baz: false
... but Helm errored out with [ERROR] templates/: template: my-chart/templates/service.yaml:20:18: executing "my-chart/templates/service.yaml" at <.Values.service.annotations>: wrong type for value; expected string; got map[string]interface {}
I tried reading the docs for the tpl function but they just showed examples for how to interpolate a string from a different variable, or how to pass values from an external file.
How do I set the annotations on this service? I don't have any control over the template.
update
You need to understand the essence of the problem, my previous answer is the style required for the final rendering, based on this, you just need to understand the usage of tpl and try to render the template as written before.
helm tpl
values.yaml
service:
annotations: "{{ toYaml .Values.service.fields | trim }}"
fields:
foo: my-foo
bar: 6
baz: false
template
apiVersion: v1
kind: Service
metadata:
name: example
namespace: {{ .Release.Namespace }}
annotations:
{{- if .Values.service.annotations }}
{{ tpl .Values.service.annotations . | nindent 4 | trim }}
{{- end }}
...
cmd
helm template --debug test .
output
apiVersion: v1
kind: Service
metadata:
name: test
namespace: default
annotations:
bar: 6
baz: false
foo: my-foo
...
As David Maze said.
In service.yaml, annotations must be a YAML string.
You may try
tempaltes/service.yaml
apiVersion: v1
kind: Service
metadata:
name: example
namespace: {{ .Release.Namespace }}
{{- if .Values.service.annotations }}
annotations:
{{ toYaml .Values.service.annotations | nindent 4 | trim }}
{{- end }}

Re-using UUID in helm configmap

There's a similar question that alludes to the possibility of auto-generating a uuid in helm charts when used as a secret or configmap. I'm trying precisely to do that, but I'm getting a new uuid each time.
My test case:
---
{{- $config := (lookup "v1" "ConfigMap" .Release.Namespace "{{ .Release.Name }}-testcase") -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-testcase"
namespace: "{{ .Release.Namespace }}"
labels:
app.kubernetes.io/managed-by: "{{ .Release.Service }}"
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/version: "{{ .Chart.AppVersion }}"
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
data:
{{- if $config }}
TEST_VALUE: {{ $config.data.TEST_VALUE | quote }}
{{- else }}
TEST_VALUE: {{ uuidv4 | quote }}
{{ end }}
I initially deploy this with:
helm upgrade --install --namespace test mytest .
If I run it again, or run with helm diff upgrade --namespace test mytest . I get a new value for TEST_VALUE. When I dump the contents of $config it's an empty map {}.
I'm using Helm v3.9.0, kubectl 1.24, and kube server is 1.22.
NOTE: I couldn't ask in a comment thread on the other post because I don't have enough reputation.
Refering to my issue where you enclosed your stack overflow post : https://github.com/helm/helm/issues/11187
A way to make your configmap work is to save as a variable before conditionnaly set your value. This means every time you upgrade, you'll generate a UUID which will normally will not be used, but this is not dramatic.
When assigning an existing value, := should become =.
Also don't forget to b64enc your value in your manifest
{{- $config := uuidv4 | b64enc | quote -}}
{{- $config_lookup := (lookup "v1" "ConfigMap" .Release.Namespace "{{ .Release.Name }}-testcase") -}}
{{- if $config_lookup -}}
{{- $config = $config_lookup.data.TEST_VALUE -}}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-testcase"
namespace: "{{ .Release.Namespace }}"
labels:
app.kubernetes.io/managed-by: "{{ .Release.Service }}"
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/version: "{{ .Chart.AppVersion }}"
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
data:
TEST_VALUE: {{ $config | quote }}

Why helm template function is not resolveing $labels var?

I am defining a PrometheusRule as follow:
prometheusRule:
rules:
- alert: SSLCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 10
for: 0m
labels:
severity: warning
annotations:
summary: Blackbox SSL certificate will expire soon (instance {{ $labels.instance }})
description: "SSL certificate expires in 30 days\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
And the template yml from helm chart:
{{- if .Values.prometheusRule.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ template "prometheus-blackbox-exporter.fullname" . }}
{{- with .Values.prometheusRule.namespace }}
namespace: {{ . }}
{{- end }}
labels:
{{- include "prometheus-blackbox-exporter.labels" . | nindent 4 }}
{{- with .Values.prometheusRule.additionalLabels -}}
{{- toYaml . | nindent 4 -}}
{{- end }}
spec:
{{- with .Values.prometheusRule.rules }}
groups:
- name: {{ template "prometheus-blackbox-exporter.name" $ }}
rules: {{ tpl (toYaml .) $ | nindent 8 }}
{{- end }}
{{- end }}
when I run helm template, tpl func it is not resolving the $labels and $values vars. When I remove annotations then the helm template is not complaining anymore. Where do fail?
error:
Error: template: prometheus-blackbox-exporter/templates/prometheusrule.yaml:18:16: executing "prometheus-blackbox-exporter/templates/prometheusrule.yaml" at <tpl (toYaml .) $>: error calling tpl: error during tpl function execution for "- alert: SSLCertExpiringSoon\n annotations:\n summary: Blackbox SSL certificate will expire soon (instance {{ $labels.instance\n }})\n expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 10\n for: 0m\n labels:\n release: prometheus\n severity: warning\n- alert: SSLCertExpiringSoon\n annotations: null\n expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 3\n for: 0m\n labels:\n severity: critical": parse error at (prometheus-blackbox-exporter/templates/prometheusrule.yaml:3): undefined variable "$labels"
Prometheus's alerting rules also use {{ ... $variable ... }} syntax, similar to Helm but with a different variant on the Go text/template syntax. When you pass this file through tpl, Helm tries to evaluate the embedded {{ ... }} template and evaluate any blocks there. Since $labels and $value aren't local variables defined at the Helm level, you get this error.
If you just want Prometheus to see this file as-is, and you don't need to replace anything at the Helm level (the file doesn't include references to .Values) then you don't need tpl
rules: {{ toYaml . | nindent 8 }}
If you do need tpl, then inside the included file you need to cause {{ to be emitted as a string and not processed as a template. One syntactic approach to it is to create a template block that prints out {{:
description: "VALUE = {{ "{{" }} $value }}"
# ^^^^^^^^^^ a {{ ... }} block that prints "{{"
The working version of syntax is as follow:
{{ `{{` }} $value }}

Helm: "Template" keyword

Can someone explain to me what the role of the keyword "template" is in this code :
apiVersion: v1
kind: Secret
metadata:
name: {{ template "identity-openidconnect" . }}
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "microService.name" . }}
release: "{{ .Release.Name }}"
xxxx
xxxxxxxxxxxx
The keyword "template" means, that Helm will find the previously created template and complete the yaml file according to the template in the template. It has to be created in advance. This type of construction allows you to refer to the same scheme many times.
For example, we can define a template to encapsulate a Kubernetes block of labels:
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
Now we can embed this template inside of our existing ConfigMap, and then include it with the template action:
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
When the template engine reads this file, it will store away the reference to mychart.labels until template "mychart.labels" is called. Then it will render that template inline. So the result will look like this:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: running-panda-configmap
labels:
generator: helm
date: 2016-11-02
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
Note: a define does not produce output unless it is called with a template, as in this example.
For more info about templates you can read this page.