Use variable in HELM template define function - kubernetes-helm

Is it possible to use a variable inside the template define function? I attempted to wrap the variable in brackets but it seems to fail. Example
{{- define {{ .Chart.Name }}.deployment -}}

The names of template functions are always fixed strings. (This is common with almost all programming languages.) Since these names don't appear anywhere in the rendered YAML, it doesn't really matter what they're called. The only place there's a potential conflict is if your chart includes other subcharts as dependencies, or is included as a subchart; in that case all template functions share the same function namespace.
A common convention is to name templates following the current chart name; that is, matching the fixed string in the Chart.yaml file
{{- define "mychart.deployment" -}}
Using the Helm include function you can call templates with a dynamic name, but this is a somewhat unusual use.

values.yaml
global:
key: {}
deployment: appdeployv1
If you were to expand the name of the chart, you would do it like this
_helpers.tpl
{{- define "key.name" -}}
{{- default .Chart.Name .Values.deployment | trunc 63 | trimSuffix "-" -}}
{{- end -}}
That's all I can understand from your question. I hope this helps. You can try exploring docs and check Declaring and using templates with define and template heading for detailed info.

Related

How does "template" and "include" differ in helm

How does "template" and "include" keywords differ in helm. Both seem to be used to render template parameters
template is part of the core Go text/template language. It always renders its results to the template output; it does not produce a value and its result cannot be captured in a variable or included in a pipeline.
include is a Helm extension. It captures the template output and returns it as a string. Its result can be used the same way as any other support function call. include is not a "keyword" or "action" or "special form", from the point of view of the templating language it is an ordinary extension function.
If you're unsure, in the context of a Helm chart, include is usually not wrong.
The most important place where this difference matters is where you have a block that produces a YAML fragment, and you need to indent it. Helm includes an indent function that can do this, but it needs a string to do this, so you need to use include and not template here.
{{- define "foo.labels" -}}
foo: bar
{{ end -}}
metadata:
labels:
{{ include "foo.labels" . | indent 4 }}
spec:
template:
metadata:
{{ include "foo.labels" . | indent 8 }}
For a more direct example, consider a template that just quotes its parameter. If you call this with template, the template pipeline syntax applies to the template parameter. If you call it with include, it applies to the result of the template call. Combining this with indent, there's a visible difference whether you see the indentation inside or outside the quotes.
{{ define "quote" }}{{ quote . }}{{ end }}
{{/* "hello" is indented, then " hello" is quoted */}}
Template: {{ template "quote" "hello" | indent 2 }}
{{/* "hello" is quoted, then '"hello"' is indented */}}
Include: {{ include "quote" "hello" | indent 2 }}
Template: " hello"
Include: "hello"

can't access helm .Values from a named template with non global context passed in

I'm trying to use a Helm named template that I plan to include with several different contexts, and the template has many values that are the same for all contexts.
Whenever I pass a context to template or include to invoke the named template, the references to .Values do not work, which is understandable because I'm explicitly setting a lower context.
In the Helm documentation for with, it claims there is a "global" variable $ that will allow reference to the global .Values, e.g., {{ $.Values... }}. This does not work (the example below shows the error).
I've also tried defining variables (using :=) and "enclosing" the include inside that variable definition (via indentation - I don't know if it matters) to make that variable available within the named template, but this doesn't work either.
I've also tried putting these in "globals" as described here which is more of a subchart thing and this doesn't work either.
So, I'm out of Helm tricks to make this work and will sadly have to re-define these many same variable many times - which makes the entire named template solution a bit less elegant - or just go back to having largely duplicate partially-parameterized templates.
What am I missing?
$ helm version
Client: &version.Version{SemVer:"v2.9+unreleased", GitCommit:"", GitTreeState:"clean"}
Values.yaml:
---
commonSetting1: "common1"
commonSetting2: "common2"
context1:
setting1: "c1s1"
setting2: "c1s2"
context2:
setting1: "c2s1"
setting2: "c2s2"
deployment.yaml:
---
{{- define "myNamedTemplate" }}
- name: {{ .setting1 }}
image: {{ $.Values.commonSetting1 }}
{{- include "myNamedTemplate" .Values.context1 }}
{{- include "myNamedTemplate" .Values.context2 }}
$ helm template test-0.1.0.tgz
Error: render error in "test/templates/deployment.yaml": template: test/templates/deployment.yaml:7:4: executing "test/templates/deployment.yaml" at <include "myNamedTemp...>: error calling include: template: test/templates/deployment.yaml:4:19: executing "myNamedTemplate" at <$.Values.commonSetti...>: can't evaluate field commonSetting1 in type interface {}
When I do this, I tend to explicitly pass in the top-level context object as a parameter. This gets a little tricky because the Go text/template templates only take a single parameter, so you need to use the (Helm/Sprig) list function to package multiple parameters together, and then the (standard text/template) index function to unpack them.
The template definition would look like:
{{- define "myNamedTemplate" }}
{{- $top := index . 0 }}
{{- $context := index . 1 }}
- name: {{ $context.setting1 }}
image: {{ $top.Values.commonSetting1 }}
{{ end }}
When you invoke it, you would then need to explicitly pass the current context as a parameter:
{{ include "myNamedTemplate" (list . .Values.context1) }}

How can we specify custom path to .Files.Get when creating ConfigMap with Helm

I am creating a config map as below
kubectl create configmap testconfigmap --from-file=testkey=/var/opt/testfile.txt
As I am using helm charts, I would like to create the config map using YAML file instead of running kubectl.
I went through Kubernetes - How to define ConfigMap built using a file in a yaml? and we can use .Files.Get to access the files.
But then testfile.txt needs to be a part of helm. I would like to have something like
kind: ConfigMap
metadata:
name: testconfigmap
data:
fromfile: |-
{{ .Files.Get "/var/opt/testfile.txt" | indent 4 }}
It works when "testfile.txt" is under the main helm directory. So, {{ .Files.Get "testfile.txt" | indent 4 }} works but {{ .Files.Get "/var/opt/testfile.txt" | indent 4 }} doesn't. With custom path, the value for the ConfigMap is empty.
Is is possible to place the file at a custom path outside the helm folder, so I can define my path in Values.yaml and read it in my ConfigMap yaml ?
This is a Community Wiki answer so feel free to edit it and add any additional details you consider important.
As mdaniel has already stated in his comment:
Is is possible to place the file at a custom path outside the helm
folder no, because helm considers that a security risk – mdaniel 2
days ago
You can also compare it with this feature request on GitHub where you can find very similar requirement described in short e.g. in this comment:
I have this exact need. My chart publishes a secret read from file at
/keybase. This file is deliberately not in the chart.
I believe files for .Files.Get should not be assumed to be inside the
chart ...
One interesting comment:
lenalebt commented on Dec 23, 2017 I am quite sure .Files.Get not
being able to access the file system arbitrarily is a security
feature, so I don't think the current behaviour is wrong - it just
does not fulfill all use cases.
This issue was created quite long time ago (Dec 19, 2017) but has been recently reopened. There are even some specific proposals on how it could be handled:
titou10titou10 commented on Apr 2 #misberner can you confirm that
using--include-dir =will allow us to use
.Files.Glob().AsConfig(), and so create a ConfigMap with one
entry in the CM per file in?
#misberner misberner commented on Apr 2 Yeah that's the idea. An open
question from my point of view is whether an --include-dir with a
specified introduces an overlay, or shadows everything under
/ from previous args and from the bundle itself. I'm not super
opinionated on that one but would prefer the former.
The most recent comments give some hope that this feature might become available in future releases of helm.
As mdaniel and mario already mentioned, for now this is not possible, as it's considered a security risk.
But actually there is a workaround.
You can use Helm templating to parse your property file and load it into a ConfigMap.
# create the following ConfigMap in your Chart
# this is just a simple prototype
# it requires strict key=value syntax in your property file (no empty strings etc.)
# but it shows the idea - improve the syntax, if needed
apiVersion: v1
kind: ConfigMap
metadata:
name: example
data:
{{- if .Values.example.map }}
{{- range $line := splitList "\n" .Values.example.map }}
{{- $words := splitList "=" $line }}
{{- $key := index $words 0 | trim }}
{{- $value := rest $words | join "=" | trim }}
{{ $key }}: "{{ $value }}"
{{- end }}
{{- end }}
{{- end }}
And after that you may load your properties file into this ConfigMap.
helm install mychart --set-file example.map="/test/my.properties"
Of course it is safe to use ONLY if you fully control the input, i. e. how each and every line of your property file is populated.

Helm 3: Access /files of library chart

I have a library chart that is used as a dependency in multiple other charts. I already share a few named templates using this. I have a few files that I also want to share using the library chart. The content of the file should end up in a config map.
I tried adding a /files directory to the library chart (next to the /templates directory), placed the files inside and used the following named template
{{- define "lib-chart.all-files-as-data" -}}
data:
{{- range $path, $bytes := $.Files.Glob "files/*" }}
{{ $path | base }}: |{{ (tpl ($bytes | toString) $ ) | nindent 6 }}
{{- end -}}
{{- end -}}
This however only picks up files from the chart which uses the library chart and not the library chart itself.
Is there a way to access the content of the /files folder of a library chart?
Thanks!
I too came across similar kind of scenario
As i checked in the internet i could able to find a way to access files in library chart
Best possible way is you can create a subchart and extract them to your chart
How to create subchart in helm you see on 1
How to access subchart variables you can read 2

Helm Charts - How do I use `default` on undefined object property values?

Using Helm, I was under the impression default would be the fallback if a variable is not defined. However, it doesn't appear Helm can get to values in sub-object hashes:
type: {{ default "NodePort" .Values.fpm.service.type }}
If .Values.fpm.service or service.type is not defined, it should use 9000.
However, attempting to template this throws a nil pointer error:
<.Values.fpm.service.type>: nil pointer evaluating interface {}.type
Is there a way to simply perform this level of variable testing? Or am I subjected to an if/else test?
The intent of this is to optionally define .fpm.service (and [..].type) within your values.yaml file.
(I'm building a Helm Library chart to handle optional definitions by main charts)
According to the official Helm doc (Using Default Function), the syntax is different and you should use it this way:
type: {{ .Values.fpm.service.type | default "NodePort" | quote }}
Doesn't look like there's really a good way to stop Helm from trying to dive into non-existing objects. I moved into a single line if condition, and it worked:
type: {{ if .Values.fpm.service -}} {{ .default "NodePort" .Values.fpm.service.type | quote }} {{- else -}} "NodePort" {{- end }}
This way, I check if fpm.service exists first, before trying .type check. It works, whether .service and .service.type is or is not defined.