How to create template in the helm chart? - kubernetes

I need to create template for secret value.
This is the secret file:
apiVersion: v1
kind: Secret
metadata:
name: secret
type: Opaque
stringData:
"user": "user"
"password": "password"
And this is what I have created in the _helpers.tpl
{{/*
User
*/}}
{{- define "##chartName##.user" -}}
{{- $secret := lookup "v1" "Secret" .Release.Namespace "secret" -}}
{{- if $secret -}}
{{- print $secret.stringData.user}}
{{- else -}}
{{- fail "The secret is absent" -}}
{{- end -}}
{{- end -}}
But it doesn't work and I get the error : <$secret.stringData.user>: nil pointer evaluating interface {}.user
I have created the secret before installation helm chart and I don't know what is the reason of this error. Thanks for any help!

You will get that error if $secret exists, but doesn't contain stringData.
At a Helm level, you can work around this by using the default function to force it to exist ("doesn't have stringData" and "doesn't have user" are approximately the same error):
{{- $secret := lookup "v1" "Secret" .Release.Namespace "secret" | default dict -}}
{{- $stringData := $secret.stringData | default dict -}}
{{- $stringData.user | required "The secret is absent" -}}
At a higher-level, if it's possible to read back the Secret, it probably only has the base64-encoded data field (even if it was created with stringData). Helm provides a b64dec function that could decode it. It'd be better to refer to the secret as an environment variable in your pod spec, or pass the value directly into Helm.
# in a Pod spec; without using Helm `lookup`
env:
- name: USER
valueFrom:
secretKeyRef:
name: secret
key: user
# or passed directly via Helm
{{- $credentials := .Values.credentials | default dict -}}
{{- $credentials.user | required "missing user.credentials" -}}

Related

Query Values in helm template

I am trying to generate a TLS certificate for all of the ingress resources in my Helm chart. My helm chart contains an application with multiple backends so my Values.yaml is structed like this:
backend1:
ingress:
host: testing.app.com
tls:
- secretName: my-tls-cert
hosts:
- testing.app.com
backend2:
ingress:
host: testing.app.com
tls:
- secretName: idp-cts-cert
hosts:
- idp-cts
db
creds: ""
serviceName: ""
Notice there is a mixture of maps and string values. My goal is to use a utility template I wrote to call genSignedCert and generate one TLS cert that has the hosts listed as a CN or alternate name:
{{/*
Return a self-signed TLS certificate
{{ include "common.certs.ingress-tls" .hosts }}
*/}}
{{- define "common.certs.gen-cert" -}}
{{- $hostlist := toStrings . -}}
{{- $cn := (first $hostlist) -}}
{{- $altnames := uniq (rest $hostlist) -}}
{{- $ca := genCA "idp-ca" 365 -}}
{{- $cert := genSignedCert $cn nil $altnames 365 $ca -}}
tls.crt: {{ $cert.Cert | b64enc }}
tls.key: {{ $cert.Key | b64enc }}
{{- end -}}
I have tried iterating over the Values and but I cannot come up with workable code to do this.
Edit1: I am aware of the security implications of using self-signed certificates. The bad values.yaml structure is inherited from the fact that this is an umbrella chart and each backed is also it's own chart. A refactor of the charts structure may be required, but I wanted to exhaust all options first.
Consider generating the TLS certificate outside Helm, and injecting it via values (or storing its components in a Secret directly). This avoids some complicated code here. There is a more serious problem, though: every time you call genCA and genSignedCert it creates a new certificate, so every time you upgrade you'll get a different certificate, and for that matter if you call this template once per Ingress object, each will have a different certificate.
It'd help this problem to restructure the values.yaml slightly. It's hard for code to tell that backend1 is a backend specification, but serviceName isn't. If you just have a list of backends this gets easier:
backends:
- ingress:
host: testing.app.com
...
- ingress:
host: testing.app.com
...
You'll then hit a couple of limitations of Helm templates as a full-featured programming language. Templates only ever return strings, so you can't write a template that returns a list. You can't pass a function as a parameter to a template, so you can't write a general-purpose map (in limited cases you can pass a template name and include it).
What you can do is write a recursive function that passes the partial list forward to the next iteration, and then invokes the final generator when it's done. In Python, we might write:
def generateCertificate(backends, tls, hosts):
# If `tls` is non-empty, take the first item from it and add its
# hosts to the `hosts` list; then recurse with the same backend
# list, the remaining `tls` items, and the updated `hosts`:
if len(tls) > 0:
return generateCertificate(backends, tls[1:], hosts + tls[0].hosts)
# If `tls` is empty but `backends` is non-empty, take the first
# backend, and recurse with the remaining `backends`, the `tls` items
# from the selected backend, and the same `hosts`:
else if len(backends) > 0:
return generateCertificate(backends[1:], backends[0].tls, hosts)
# If `tls` and `backends` are both empty, we're done
else:
return buildTheCertificate(hosts)
certificate = generateCertificate(values.backends, [], [])
We can convert this logic into Go templates:
{{/* Emit a TLS certificate given the list of backends. The
parameter is a dictionary with keys `backends`, `tls`, and `hosts`. */}}
{{- define "common.certs.gen-cert" -}}
{{- if .tls -}}
{{- include "common.certs.gen-cert" (dict "backends" .backend "tls" (last .tls) "hosts" (concat .hosts (head .tls).hosts)) -}}
{{- else if .backends -}}
{{- include "common.certs.gen-cert" (dict "backends" (tail .backends) "tls" (head .backends).tls "hosts" .hosts) -}}
{{- else -}}
{{- include "common.certs.gen-cert-hosts" .hosts -}}
{{- end -}}
{{- end -}}
{{/* Actually generate a TLS certificate from a list of host names.
Note, the certificate will be regenerated on every call. The
single parameter is a list of names. */}}
{{- define "common.certs.gen-cert-hosts" -}}
{{- $cn := first . -}}
{{- $altnames := rest . | uniq -}}
{{- $ca := genCA "idp-ca" 365 -}}
{{- $cert := genSignedCert $cn nil $altnames 365 $ca -}}
tls.crt: {{ $cert.Cert | b64enc }}
tls.key: {{ $cert.Key | b64enc }}
{{- end -}}
{{- include "common.certs.gen-cert" (dict "backends" .Values.backends) -}}
This is enough intricate code that it's probably worth unit-testing it. Setting this up is left as an exercise; Helm does not have any sort of native support here.

Create configmap with outside yaml files for kubernetes

I'm quite new for kubernetes. I am trying to create configmap with using yaml file which was user defined.
helm upgrade --install test --namespace test --create-namespace . -f xxx/user-defined.yaml
user can add any yaml file with using 'f' option.
for example;
cars.yaml
cars:
- name: Mercedes
model: E350
So command will be;
helm upgrade --install test --namespace test --create-namespace . -f xxx/cars.yaml
My question is, I want to create configmap which is name 'mercedes-configmap'
I need to read that values from cars.yaml and create automaticaly configmap with name and data of cars.yaml
Update,
I've created below configmap template;
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.cars.name }}-configmap
data:
{{- range .Files }}
{{ .Files.Get . | toYaml | quote }}
{{- end }}
The only issue that I faced, I couldnt get the whole file data.
Welcome to the community!
I have created a helm template for configmap. It works this way: you can pass configmap name - name and file name - fname where data is stored and/or it can read files from a specific folder.
Please find the template (first 3 lines are commented, it's two working implementations of logic to check values existing):
{{/*
{{ if not (or (empty .Values.name) (empty .Values.fname)) }}
*/}}
{{ if and (not (empty .Values.name)) (not (empty .Values.fname)) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.name }}-configmap
data:
{{- ( .Files.Glob .Values.fname ).AsConfig | nindent 2 }}
---
{{ end }}
{{ $currentScope := .}}
{{ range $path, $_ := .Files.Glob "userfiles/*.yaml" }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ base $path | trimSuffix ".yaml" }}-configmap
data:
{{- with $currentScope}}
{{ base $path }}: |
{{- ( .Files.Get $path ) | nindent 4 }}
{{- end }}
---
{{ end }}
First part of the template checks if configmap name and file name are set and if so, it renders it. Second part goes to userfiles directory and gets all yamls within.
You find github repo where I shared file examples and configmap.
To render the template with cars2.yaml and with/without files within userfiles directory:
helm template . --set name=cars2 --set fname=cars2.yaml
To render the same template with only files in userfiles directory:
helm template .
P.S. helm v3.5.4 was used
Useful links:
Accessing files in helm
Flow control
File path functions

What is the difference between fullnameOverride and nameOverride in Helm?

I could find both fullnameOverride and nameOverride in Helm chart.Please help clarifying what is the difference between these two with an example.
nameOverride replaces the name of the chart in the Chart.yaml file, when this is used to construct Kubernetes object names. fullnameOverride completely replaces the generated name.
These come from the template provided by Helm for new charts. A typical object in the templates is named
name: {{ include "<CHARTNAME>.fullname" . }}
If you install a chart with a deployment with this name, and where the Chart.yaml file specifies name: chart-name...
helm install release-name ., the Deployment will be named release-name-chart-name
helm install release-name . --set nameOverride=name-override, the Deployment will be named release-name-name-override
helm install release-name . --set fullnameOverride=fullname-override, the Deployment will be named fullname-override
The generated ...fullname template is (one code branch omitted, still from the above link)
{{- define "<CHARTNAME>.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
So if fullnameOverride is provided, that completely replaces the rest of the logic in the template. Otherwise the name is constructed from the release name and the chart name, where nameOverride overrides the chart name.

Helm change name of deployments artiefacts

I’ve created helm chart which is working as expected, however I want to change the names of the deployed
application
currently for each deployment I got different (random) name and I want it to be a fixed name, how can I do that?
This is the helper
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "unleash.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some K8S name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "unleash.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
This is the outcome of the name after deployment
crabby-ibex-postgresql-0 0/1 Pending 0 1s
crabby-ibex-unleash-86775cdffd-xt575 0/1 ContainerCreating 0 1s
This is the names from the values yaml
replicaCount: 1
namespace: unleash
restartPolicy: Never
name: a-unleash
nameOverride: unleash
e.g. I want it instead of
crabby-ibex-unleash-86775cdffd-xt575
to be like
unleash-service
uleash-postgressql
update
I've added the following to the _helper.tpl
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 26 | trimSuffix "-" -}}
{{- end -}}
and put the following in the values.yml
fullnameOverride: apps
i expect that the artifacts will start with apps and it doesnt work
I don't know, why nobody posted it yet. You can pass the name of the Helm release to the helm install command:
helm install <your_Chart.yaml_directory> -n <release_name>
Based of name crabby-ibex-unleash-86775cdffd-xt575 I guess you are using kind: Deployment for this application, if you change kind to StatefulSet in you yaml you will end up with pod named uleash-postgressql-0, but because of helm you have additional prefix, you could use --name=you_relese_name which will create pod you_relese_name-uleash-postgressql-0.
If you really want to get rid of helm chart prefix you have to set fullnameOverride for every chart you are deploying.
EDIT:
To make use of fullnameOverride you have to define it in your _helpers.tpl file.
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "unleash.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some K8S name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "unleash.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 26 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

helm getting subchart service names

Whats the best way to get the helm subchart service names to reference into my ingress controller that will sit in the parent chart
values.yaml
---
ingress:
paths:
- serviceName: app-1
path: /app-1/*
port: 8080
- serviceName: app-2
path: /app-2/*
port: 8080
ingress.yaml
---
{{- range .Values.ingress.paths }}
- path: {{ .path }}
backend:
{{- $subchart := .serviceName -}}
serviceName: {{- include "$subchart.fullname" .}}
servicePort: {{ .port }}
{{- end }}
template: no template "$subchart.fullname" associated with template "gotpl"
helm 3.7 version has solved the problem
https://github.com/helm/helm/pull/9957.
You can use like this
{{ template "bar.fullname" .Subcharts.bar }}
How about hardcoded subchart name scoped by release ?
{{ .Release.Name }}-<subchart_name>
I have found that the best way to reference a service name is to override the template that they are using. There are some caveats to doing this however.
The subchart and your chart will have different contexts so they will most likely render the template differently
There are some things that are only available to the subchart
Most charts have a template similar to the one below in their _helpers.tpl file.
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "newchart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
The subchart has different .Values to your chart. We will fix this when we render this template by creating a context that is similar to the subcharts context.
Instead of calling it with the . context we create a new context by replacing the .Values with the subcharts .Values.
{{ template "newchart.fullname" (set (deepCopy .) "Values" .Values.newchart }}
We use deepCopy so that we don't actually change the . context but rather create a new one to use.
The subchart has access to its own .Chart values that we can't replicate. In this case we will have to hardcode the value of .Chart.Name to the template. In this we can just replace it with the chart name newchart.
Once we have done this both nameOverride and fullnameOverride on the subchart will work without you having to manually change anything in your template files.
If the subchart uses the fullname function from _helpers.tpl (provided by helm by default for new charts) you can use this (replace postgresql with the name of the subchart):
{{- $fullName := include "postgresql.fullname" (mustMerge (dict "Chart" (dict "Name" "postgresql") "Values" .Values.postgresql) (deepCopy .)) -}}
It depends on the sub-chart definition!
As an example, elasticsearch chart, see here https://github.com/elastic/helm-charts/blob/master/elasticsearch/templates/service.yaml, is defining 2 services.
Both services name can be declared as value clusterName.