Helm: It is possible to use nindent function with template function? - yaml

I'm a bit new to helm syntax and I'm trying to include a template in my deployments file. In some of my deployments the indent must be different. Is there a way to include my template and use indent function as well?
{{ template "my-chart.nodeaffinity" include "my-chart.fullname" . | toYaml | nindent 8 }}
It is possible to use the template and toYaml function in the same statement?
Thanks!
I have tried to add the bellow code but is not working and when I use helm template the nindent function is not working.
| toYaml | nindent 8

You can't really use template and toYaml together as you describe. What you can do is use the similar but Helm-specific include function in place of template:
{{ include "my-chart.nodeaffinity" (include "my-chart.fullname" .) | toYaml | nindent 8 }}
{{/*^^^^^^ not template ^--- need parens here ---^ */}}
template is a built-in Go text/template action. It consumes the pipeline in the rest of its block, and it always sends its output to the rendered template output. So {{ template "name" "arg" | toYaml }} reads "arg" | toYaml as the parameter to the template, and there's no way to process its output.
include is an extension function in the template syntax. It renders a template in the same way template does, but it returns the rendered string, which can then participate in pipelines. So {{ include "name" "arg" | toYaml }} calls the template with exactly the specified argument, and then processes the return value.
One corollary to this is that include always returns a string. Even if it looks like the helper template is producing a YAML object, it's really a string. This might mean you don't want toYaml here, since it could cause extra escaping you don't need.
You can see a more concrete example of this in the default chart scaffolding. A standard set of labels is defined as a template
{{- define "<CHARTNAME>.labels" -}}
helm.sh/chart: {{ include "<CHARTNAME>.chart" . }}
...
{{- end }}
When this is used, it's used via include (not template), and its output is (n)indented
metadata:
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}

Related

Merge annotations in Helm

Assuming I have the following values.yaml for a subchart:
global:
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header yyy "yyy";
tag: 123
port: 1234
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header xxx "xxx";
...
how can I merge both annotation blocks in the ingress.yaml template together so that it results in:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations: |
add_header xxx "xxx";
add_header yyy "yyy";
...
Has anyone a hint where to start here?
Both values are just strings, and you can either write them one after another or concatenate them at the template layer.
So, for example, a minimal thing that should come close to working, for this specific example, could be
{{- $key := "nginx.ingress.kubernetes.io/configuration-snippet" -}}
metadata:
annotations: |
{{ index .Values.ingress.annotations $key | trim | indent 4 }}
{{ index .Values.global.ingress.annotations $key | trim | indent 4 }}
This takes advantage of the fact that Helm doesn't really "understand" YAML at the templating layer; instead, a template writes out an arbitrary string and them Helm tries to parse it afterwards. So you can use the YAML | block scalar marker, and then write out arbitrary content under it, and so long as you get the indentation right it will work.
The question sounds like it's reaching for a more general question of how to merge the two annotation lists, combining individual values by concatenating the strings. Helm includes a set of dictionary template functions, which somewhat unusually work by mutating a dictionary in-place. You can combine this with the underdocumented toYaml function to write the dictionary in valid YAML syntax.
In pseudocode, I might write:
Create a new dictionary that's a copy of the local annotations.
Loop through the global annotations. For each, if the key does not exist, save its value, but if it does, append the global value.
Write the result as YAML.
You could translate this into Helm template code:
{{- $annotations := deepCopy .Values.ingress.annotations -}}
{{- range $key, $value := .Values.global.ingress.annotations -}}
{{- $existing := get $annotations $key -}}
{{- $new := cat $existing $value -}}
{{- $_ := set $annotations $key $new -}}
{{- end -}}
metadata:
annotations:
{{ $annotations | toYaml | indent 4 }}
In particular here I've taken advantage of the property that get returns an empty string if the key doesn't exist, which happens to be what you want in this particular case. For other "reduce values" type operations you might need to check if the value exists using hasKey, or use a default value.

Helm template for Vault annotation

I'm trying to nest one template into another, namely I have a k8s deployment.yaml which should be templated by helm. However, it is already using Vault template in annotation field responsible for injecting secrets into the pod.
Without Helm template it looks like this:
vault.hashicorp.com/agent-inject-template-.env: |
{{- with secret (print "envs/data/test") -}}{{- range $k, $v := .Data.data -}}
{{ $k }}={{ $v }}
{{ end }}{{- end -}}
Now, I would like to make a template for Helm and replace path envs/data/test with value coming from Helm values. So I've tried to use it like that:
vault.hashicorp.com/agent-inject-template-.env: |
{{- with secret (print {{ .Values.path }}) -}}{{- range $k, $v := .Data.data -}}
{{ $k }}={{ $v }}
{{ end }}{{- end -}}
This however does not work meaning {{ .Values.path }} is outputed as is, as a string.
How can I template over another template and resolve {{ .Values.path }} as a variable?

How to concat string to result of .AsConfig in helm?

I have config like this:
{{- with .Files.Glob "files/my-files/*.json" }}
{{ .AsConfig | indent 2 }}
{{- end }}
In the end of each file I want to add "FIHISHED!"
How can I achieve it in helm ?
The .AsConfig method renders and returns all files as a single YAML text. So you can't format the result.
If you want to list all files (with content), separated with an arbitrary text, I suggest to do this "yourself". Files is a map of files, mapping from string name to []byte content.
{{- with .Files.Glob "files/my-files/*.json" }}
{{ range $name, $content := . -}}
{{ printf "-%s:\n%s\nFINISHED!" $name $content | indent 2 }}:
{{- end }}
{{- end }}

How to return a map object from template in Helm function?

I have a function that I want to call in another function. I want it to return a map, but instead it returns the toString representation of the map. It is an actual map inside the function, but not outside it.
{{- define "app.getSubKey" -}}
{{- $name := .source }}
{{- range $key, $value := .keys }}
{{- if kindIs "int" $value }}
{{- $name = index $name (int $value) }}
{{- else }}
{{- $name = index $name $value }}
{{- end }}
{{- end }}
{{- if kindIs "string" $name }}
{{- trim $name }}
{{- else }}
{{ $name }}
{{- end }}
{{- end }}
When I call this function on the below YAML with {{- include "app.getSubKey" (dict "source" .Values.vars "keys" (list 0)) }}, inside the function $name is a map (I used kindOf to check this) but outside it's of kind string. How can I get it returned as a map?
vars:
- name: something
value: blah
To be precise: your app.getSubKey is not a function, it is a named template and as such, it does not return anything. On the other hand, include is a function. It takes as arguments a template name and its context, and returns rendered template as a string. You cannot change the type of the include function return value.
Said that, I see two options:
if your goal is to get a properly formatted YAML from the include "app.getSubKey" call (still as a string) use toYaml function inside the named template, i.e.:
{{- define "app.getSubKey" -}}
...
{{- else }}
{{- $name | toYaml | nindent 0 }}
{{- end }}
{{- end }}
nindent 0 is necessary to get a properly left align YAML
if you want to get an actual dictionary (for further processing), you need to convert the string returned from the include call back to an object (the toYaml in the named template is still required):
{{- $d := dict "source" .Values.vars "keys" (list 0) | include "app.getSubKey" | fromYaml -}}
The function fromYaml is undocumented, so using it might be a bit risky. The only trace of it I found in the official docs is in the examples on the library charts page.
BTW, the app.getSubKey logic doesn't seem correct: it will fail for the keys list with more then one element.
If app.getSubKey returns an array, you must use fromYamlArray to "decode" the data.

Merge two Value files in helm

I'm looking to merge two values files in helm.
secrets.yaml:
serviceMonitor:
endpoints:
- module: oracledb
port: http
scheme: http
url: "http://user:password#ip:port/xxx"
I have another values.yaml file which has multiple endpoints. I want to merge both the values files. I'm tried using append function to do that: {{$endpoints := (append .Values.serviceMonitor.endpoints .Values.oracle_db.serviceMonitor.endpoints) }} When I do a dry-run, I see its picking up both the values but won't merge. Any one come across this?
In the current Helm version (3) merging values is not supported.
This feature was discussed in this Github issue: Helm should preform deep merge on multiple values files.
One important quote from there
If this is implemented, it should be optional, i. e. the merge mode should be specified as a command-line flag. Anything else would be a breaking change and, I think, in most cases not desirable. The problem is that you would not be able to override defaults for lists.
See: https://github.com/helm/helm/issues/3486#issuecomment-364534501
You can use a python script to merge the values files before passing them. Below is a code snippet of what I am using.
import yaml
from deepmerge import always_merger
fileA = “tmp.yaml"
fileB = “feature.yaml"
with open(fileA,'r+') as f:
fileAdictionary= yaml.load(f)
with open(fileB,'r+') as f:
fileBdictionary = yaml.load(f)
result = always_merger.merge(fileAdictionary, fileBdictionary)
with open(‘newFile.yaml’,'w+') as f:
yaml.dump(result,f)
another option:
{{- define "template.valueOrDefault" -}}
{{- $value := dict -}}
{{- range (rest .) -}}
{{- $value = merge $value . -}}
{{- end -}}
{{- if $value -}}
{{- printf "%s:" (first .) }}
{{- toYaml $value | nindent 2 }}
{{- end }}
{{- end -}}
usage:
containers:
- name: db-migrations-job
{{- include "template.valueOrDefault" (list "resources" .Values.migrationJob.resources .Values.resources) | nindent 8 }}

Resources