Why helm template function is not resolveing $labels var? - kubernetes-helm

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

Related

HELM: include named template inside tpl .Files.Get fails

structure of folders:
files
alertmanager
rules
- alertmanager.rules
- nodes.rules
...
templates
- _helpers.tpl
- prometheus.yaml
files/alertmanager/rules/alertmanager.rules
- name: Alertmanager
rules:
- alert: PrometheusAlertmanagerConfigurationReloadFailure
expr: |
# Without max_over_time, failed scrapes could create false negatives, see
# https://www.robustperception.io/alerting-on-gauges-in-prometheus-2-0 for details.
max_over_time(alertmanager_config_last_reload_successful{app_kubernetes_io_name="alertmanager"}[5m]) == 0
for: 10m
labels:
severity: critical
{{ include _default_rule_labels . }}
annotations:
type: Alertmanager
summary: Prometheus AlertManager configuration reload failure
description: |
The error could be caused by recent changes and could be caused by an incorrect configuration of alertmanager template (defined in templates/prometheus.yaml).
Or it can be caused by incorrect route(s) configuration (typically in argocd/apps/values.yaml)
templates/_helpers.tpl
{{/*
Collect alertmanager rules from files
*/}}
{{- define "alertmanager.rules" -}}
{{- range $path, $_ := .Files.Glob "files/alertmanager/rules/**.rules" }}
{{ tpl ($.Files.Get $path) $ }}
{{- end }}
{{- end }}
{{/*
Set default alert rule labels
*/}}
{{- define "_default_rule_labels" -}}
environment: {{ .Values.environment }}
client: {{ .Values.client }}
cluster: {{ .Values.eks_cluster }}
sla: {{ .Values.sla }}
{{- end }}
templates/prometheus.yaml
# ? PROMETHEUS ALERT RULES
serverFiles:
alerting_rules.yml:
groups:
{{- include "alertmanager.rules" . | nindent 14 }}
I'm getting the following error when trying to render the prometheus.yaml template:
✗ helm template . -s templates/prometheus.yaml
parse error at (root/templates/prometheus.yaml:15): function "_default_rule_labels" not defined
How to approach?
I can render {{ .Values.something }} inside files/alertmanager/rules/alertmanager.rules, but inclusion of named templates throws an error.

how to loop through in Helm chart

I was trying to iterate through my values.yaml file objects
values.yaml
keyvault:
name: mykv
tenantId: ${tenantId}$
clientid: "#{spid}#"
clientsecret: "#{spsecret}#"
secrets:
- secret1
- secret2
- secret3
So here my requiremnt is to loopthrough each item of above "secrets" to below serviceproviderclass templates, "Data" and objectName fields. But its failing.
What I tried is as below.
{{- define "commonobject.secretproviderclass.tpl" -}}
{{- if eq .Values.secret.enabled true }}
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: {{ $.Release.Name }}-secretproviderclass
labels:
app: {{ $.Release.Name }}
chart: "{{ $.Release.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
provider: azure
secretObjects:
- data:
{{- range $key, $value := .Values.keyvault }}
{{- range $value.secret }}
- key: { . }
{{- end }}
{{- end }}
secretName: {{ .Values.secret.keyvault.name }}
type: opaque
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "false"
userAssignedIdentityID: ""
keyvaultName: {{ .Values.keyvault.name }}
objects: |
array:
{{- range $key, $value := .Values.keyvault }}
{{- range $value.secret }}
- |
objectName: {{ . }}
objectType: secret
{{- end }}
{{- end }}
tenantId: {{ .Values.keyvault.tenantid }}
{{- end }}
{{- end -}}
{{- define "commonobject.secretproviderclass" -}}
{{- template "commonobject.util.merge" (append . "commonobject.secretproviderclass.tpl") -}}
{{- end -}}
In your values.yaml file, .Values.keyvault is a single object that has keys name, secrets, etc. If you want to iterate through its secrets you just need a single range loop to do that.
secretObjects:
- data:
{{- range .Values.keyvault.secrets }}{{/* <-- only one range loop */}}
- key: { . }
{{- end }}
secretName: {{ .Values.secret.keyvault.name }}
type: opaque
In the double loop you have now, $key and $value get set to each pair from .Values.keyvault in sequence; clientid and #{spid}#, clientsecret and #{spsecret}#, and so on; and then you try to iterate through those (often string-valued) values. For this use you don't need the outer loop.

Helm: overloading global variables on {{ include }} call?

I have a shared metadata block, based on the commons library that I would like to override a name for one specific instance. Is it possible?
metadata:
{{ include "common.metadata" (merge (dict ".Values.fullnameSuffix" "-redirect") .) }} # Doesn't work - How do I add a `-redirect` suffix?
name: {{ include "common.fullname" . }}-redirect # Causes two `name:` attributes
Within common.metadata there is a call to "fullname" as well:
{{ define "common.metadata" -}}
name: {{ template "common.fullname" . }}
namespace: {{ .Release.Namespace }}
{{- end -}}
Is there a way to pass-down a variable override from the first include so that I can override the name:? It's specific only to this chart.
Use set to add a new key/value pair to a dictionary and pass it to slightly modified common.metadata helper function.
values.yaml
fullnameSuffix: redirect
_helpers.tpl
{{- define "common.metadata" -}}
{{- if .suffix }}
name: {{ template "common.fullname" . }}-{{ .suffix }}
{{- else }}
name: {{ template "common.fullname" . }}
{{- end }}
namespace: {{ .Release.Namespace }}
{{- end -}}
manifest.yaml
metadata:
{{- include "common.metadata" (set . "suffix" .Values.fullnameSuffix ) }}
If your fullnameSuffix is empty, name without suffix will be used.

Using include inside range in Go templates (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 }}

ingress.yaml template returns error in renderring --> nil pointer evaluating interface {}.service

I am installing a helm chart which has a ingress.yaml template.
I get this error:
Error: render error in "chartmuseum/templates/ingress.yaml": template: chartmuseum/templates/ingress.yaml:35:22: executing "chartmuseum/templates/ingress.yaml" at <.Values.service.servicename>: nil pointer evaluating interface {}.service
I am not able to find where the problem is. The same set of if else structure works abolutely fine in the service.yaml of the same helm chart.
- path: {{ default "/" .path | quote }}
backend:
{{- if .Values.service.servicename }}
serviceName: {{ .Values.service.servicename }}
{{- else }}
serviceName: {{ include "chartmuseum.fullname" . }}
{{- end }}
Getting error on this line --> serviceName: {{ .Values.service.servicename }}
The code that works in service.yaml fine is
metadata:
{{- if .Values.service.servicename }}
name: {{ .Values.service.servicename }}
{{- else }}
name: {{ include "chartmuseum.fullname" . }}
{{- end }}
Expected result: if there is a servcice.servicename in values in values.yaml file , the ingress should pick the value from there for the key serviceName. Else it should include "chartmuseum.fullname".
The same structure works fine for service.yaml.
Below is the url of the original helm chart that i am using.
https://github.com/helm/charts/tree/master/stable/chartmuseum
I just modified the ingress.yaml to add if else block around line 31.
Ingress.yaml https://github.com/helm/charts/blob/master/stable/chartmuseum/templates/ingress.yaml
Values.yaml file is insignificant. I have the below values in it
service:
servicename: helm-charts-test
but even without this value, the if else block is expected to work.
What you're seeing is a weird caveat in Go templating. Your conditional logic is being evaluated inside a range loop. This means . you're using to access Values is not the one you expect it to be, as it's overridden for each range iteration evaluation.
You can use $, which references the global scope in order to access the Values as expected.
For your scenario, it would be something like:
- path: {{ default "/" .path | quote }}
backend:
{{- if $.Values.service.servicename }}
serviceName: {{ $.Values.service.servicename }}
{{- else }}
serviceName: {{ include "chartmuseum.fullname" $ }}
{{- end }}
See here for more details.
I followed this answer by #Torrey and replaced
targetPort: {{ .Values.non_existing.port | default 1234 }}
with
targetPort: {{ (.Values.non_existing).port | default 1234 }}
and it worked