I am trying to make a default value for CPU if its mentioned(if its not mentioned, i have handled that separately) value in values file is less than 5000m. I am trying this but I don't think I am doing it correctly.
Also, if I make the default value as 5000m and in my values file its mentioned just as 5. Would it be able to compare both?
resources:
requests:
{{- if .Values.resources.requests.cpu }}
cpu: {{ .Values.resources.requests.cpu }}
{{- end }}
{{- if .Values.resources.requests.memory }}
memory: {{ .Values.resources.requests.memory }}
{{- end }}
limits:
{{- if ((( .Values.resources).limits).cpu) }}
cpu: {{ .Values.resources.limits.cpu }}
{{- else }}
{{- $limit_value := .Values.resources.requests.cpu | toString | regexFind "[0-9.]+" }}
{{- $limit_suffix := .Values.resources.requests.cpu | toString | regexFind "[^0-9.]+" }}
cpu: {{ mulf $limit_value 3 }}{{ $limit_suffix }} }}
{{- end }}
{{- if (((.Values.resources).limits).memory) }}
memory: {{ .Values.resources.limits.memory }}
{{- else }}
{{- $limit_val := .Values.resources.requests.memory | toString | regexFind "[0-9.]+" }}
{{- $limit_suff := .Values.resources.requests.memory | toString | regexFind "[^0-9.]+" }}
memory: {{ mulf $limit_val 3 }}{{ $limit_suff }}
{{- end }}
{{- end }}
You have two separate issues here. There's not a built-in way to parse the Kubernetes resource values, so you'll have to do a lot of work to actually provide that default value.
If you just want to provide a default value and not try to check for a minimum, then you can just use the Helm (Sprig) default function:
resources:
requests:
cpu: {{ .Values.resources.requests.cpu | default "5000m" }}
The minimum bound is what leads to some trouble. I don't believe there's a function in Helm to parse 5000m, or to compare that to 5.0. You could try writing it in Helm template syntax, but it can become awkward.
{{/* Convert a resource quantity like "5000m" to a base number like "5".
Call with the quantity string as the parameter, returns the number
as a string. */}}
{{- define "resource-quantity" -}}
{{- if . | hasSuffix "m" -}}
{{- $quantity = . | trimSuffix "m" | float64 -}}
{{- divf $quantity 10000000 -}}
{{- else -}}
{{ . }}
{{- end -}}
{{- end -}}
Note that there are many suffixes besides m and you might want to handle those too, maybe using a dictionary structure. I'm using the Sprig floating-point math functions which should be included in Helm. This template is actual code and you also want to arrange things like tests for it, which Helm does not support well.
Once you have that, gt (greater-than) is a function that takes two parameters. You want to test both "is it present" and also "is it at least this minimum", so you'd have to repeat the value. For a long value like this one thing that can help is to use the standard template with operator, which both acts like an if instruction and also temporarily rebinds the . variable to the value you're testing.
So you could write something like
{{- with .Values.resources.requests.cpu -}}
{{- $quantity := include "resource-quantity" . | float64 }}
{{- if and . (gt $quantity 5.0) }}
cpu: {{ . }}
{{- else }}{{/* if */}}
cpu: 5000m
{{- end }}{{/* if */}}
{{- else }}{{/* with */}}
cpu: 5000m
{{- end }}{{/* with */}}
But with already tests if the value is non-empty, and you can use the maxf function to enforce a minimum value. So (given a complete working tested resource-quantity template function) you could write:
resources:
requests:
{{- with .Values.resources.requests.cpu }}
cpu: {{ include "resource-quantity" . | float64 | maxf 5.0 }}
{{- else }}
cpu: 5.0
{{- end }}
This is how I managed to compare. Below is the example of one of the unit m
{{- if eq $limit_suffix "m" }}
cpu: {{ mulf $limit_value 3 | max 5000 }}{{$limit_suffix}}
{{- else }}
cpu: {{ mulf $limit_value 3 | max 5 }}
{{- end }}
I am trying to refactor a Helm chart for a large enterprise application with numerous deployments, services, ingresses etc and am trying to reduce copy-and-paste. Since I've not found any generally accepted DRY design patterns for Helm charts that have enterprise applications in mind, I am simply working from this guide as starting point: https://faun.pub/dry-helm-charts-for-micro-services-db3a1d6ecb80
I would like to define a named template for each high level resource, such as a deployment and call it as needed for each component in the application that I'm deploying. Using a simple ConfigMap as an example, I have come up with the following example that works perfectly as expected if I invoke the named template only once:
{{- define "mergeproblem.configmap" -}}
{{- $ := index . 0 -}}
{{- $name := index . 2 -}}
{{- with index . 1 -}}
{{- $this := dict "Values" (get .Values $name) -}}
{{- $defaultRoot := dict "Values" (omit .Values $name) -}}
{{- $noValues := omit . "Values" -}}
{{- with merge $noValues $this $defaultRoot -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mergeproblem.fullname" . }}-{{ $name }}
labels:
{{- include "mergeproblem.labels" . | nindent 4 }}
data:
MergeProblemExample: |
Values: {{ .Values | toYaml | nindent 6 }}
Template: {{ .Template | toYaml | nindent 6 }}
Chart: {{ .Chart | toYaml | nindent 6 }}
Release: {{ .Release | toYaml | nindent 6 }}
{{- end -}}
{{- end -}}
{{- end -}}
The idea here is that I can call this named template, giving it the name of the resource I want to create. It will pull the defaults from the top-level .Values but merge in the .Values.$name over the top with a higher precedence so that the template contents can be relatively simple without having to perform individual merges or ternaries on every variable interpolation -- instead the main scoped input will already have the merging done on .Values.
An example of the working output if I make only a single call: https://pastebin.com/ZCJ4LTpV
I am invoking it with the following:
{{- template "mergeproblem.configmap" (list $ . "myfoo") -}}
However if I invoke it more than once like so:
{{- template "mergeproblem.configmap" (list $ . "myfoo") -}}
{{- template "mergeproblem.configmap" (list $ . "yourbar") -}}
I receive the following error from Helm:
$ helm upgrade --install --namespace mergeproblem --debug mergeproblem ./mergeproblem/
history.go:56: [debug] getting history for release mergeproblem
upgrade.go:123: [debug] preparing upgrade for mergeproblem
upgrade.go:131: [debug] performing update for mergeproblem
upgrade.go:303: [debug] creating upgraded release for mergeproblem
Error: UPGRADE FAILED: create: failed to encode release "mergeproblem": json: unsupported value: encountered a cycle via map[string]interface {}
helm.go:81: [debug] json: unsupported value: encountered a cycle via map[string]interface {}
create: failed to encode release "mergeproblem"
helm.sh/helm/v3/pkg/storage/driver.(*Secrets).Create
/private/tmp/helm-20210414-93729-197z3ms/pkg/storage/driver/secrets.go:156
helm.sh/helm/v3/pkg/storage.(*Storage).Create
/private/tmp/helm-20210414-93729-197z3ms/pkg/storage/storage.go:69
helm.sh/helm/v3/pkg/action.(*Upgrade).performUpgrade
/private/tmp/helm-20210414-93729-197z3ms/pkg/action/upgrade.go:304
helm.sh/helm/v3/pkg/action.(*Upgrade).Run
/private/tmp/helm-20210414-93729-197z3ms/pkg/action/upgrade.go:132
main.newUpgradeCmd.func2
/private/tmp/helm-20210414-93729-197z3ms/cmd/helm/upgrade.go:155
github.com/spf13/cobra.(*Command).execute
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:850
github.com/spf13/cobra.(*Command).ExecuteC
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:958
github.com/spf13/cobra.(*Command).Execute
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:895
main.main
/private/tmp/helm-20210414-93729-197z3ms/cmd/helm/helm.go:80
runtime.main
/usr/local/Cellar/go/1.16.3/libexec/src/runtime/proc.go:225
runtime.goexit
/usr/local/Cellar/go/1.16.3/libexec/src/runtime/asm_amd64.s:1371
UPGRADE FAILED
main.newUpgradeCmd.func2
/private/tmp/helm-20210414-93729-197z3ms/cmd/helm/upgrade.go:157
github.com/spf13/cobra.(*Command).execute
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:850
github.com/spf13/cobra.(*Command).ExecuteC
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:958
github.com/spf13/cobra.(*Command).Execute
/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/spf13/cobra#v1.1.1/command.go:895
main.main
/private/tmp/helm-20210414-93729-197z3ms/cmd/helm/helm.go:80
runtime.main
/usr/local/Cellar/go/1.16.3/libexec/src/runtime/proc.go:225
runtime.goexit
/usr/local/Cellar/go/1.16.3/libexec/src/runtime/asm_amd64.s:1371
My values.yaml looks like this:
########
#
# Component specifics
#
myfoo:
image:
repository: REPOSITORY-FROM-MYFOO-BLOCK
tag: TAG-FROM-MYFOO-BLOCK
autoscaling:
minReplicas: 33
yourbar:
image:
repository: Repository-From-Yourbar-Block
tag: Tag-From-Yourbar-Block
########
#
# Example defaults
#
replicaCount: 1
image:
repository: default-repository-from-top-level-image-block
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "default-tag-from-top-level-image-block"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
I'm trying to create my own chart library from tutorial: https://helm.sh/docs/topics/library_charts/ . In the tutorial, there is really nice function, which merges 2 yamls together:
{{- define "libchart.util.merge" -}}
{{- $top := first . -}}
{{- $overrides := fromYaml (include (index . 1) $top) | default (dict ) -}}
{{- $tpl := fromYaml (include (index . 2) $top) | default (dict ) -}}
{{- toYaml (merge $overrides $tpl) -}}
{{- end -}}
I define web deployment template as:
{{- define "libchart.web.tpl" -}}
(...)
containers:
- env:
envFrom:
- configMapRef:
name: app-configmap
- secretRef:
name: app-secrets
image: {{ include "libchart.image" . }}
imagePullPolicy: Always
name: web
resources: {}
(...)
{{- end -}}
{{- define "libchart.web" -}}
{{- include "libchart.util.merge" (append . "libchart.web.tpl") -}}
{{- end -}}
When I want to override a dictionary it's ok:
{{- include "libchart.web" (list . "web") -}}
{{- define "web" -}}
metadata:
annotations:
test1: "test1"
{{- end -}}
And now I want to override resources:
{{- include "libchart.web" (list . "web") -}}
{{- define "web" -}}
spec:
template:
spec:
containers:
- resources:
requests:
cpu: '0.01'
memory: 109.0Mi
{{- end -}}
And the error which I get is:
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: ValidationError(Deployment.spec.template.spec.containers[0]): missing required field "name" in io.k8s.api.core.v1.Container
Which means that it tries to create 2 elements on the container list.
I found closed issue on githug: https://github.com/helm/charts/issues/19855.
I'm trying to make a list of env vars from a values.yaml become a single secret.yaml file containing the list of envs of the type "secret". The idea is to only create this secret file if at least one of the types equals "secret".
Eg:
values.yaml
env:
- name: PLAIN_TEXT_ENV_VAR1
type: plain
value: text value
- name: PLAIN_TEXT_ENV_VAR2
type: plain
value: text value
- name: TOP_SECRET_ENV_VAR_1
type: secret
- name: TOP_SECRET_ENV_VAR_2
type: secret
- name: TOP_SECRET_ENV_VAR_3
type: secret
resulting secret.yaml
kind: Secret
metadata:
name: test
data:
TOP_SECRET_ENV_VAR_1: change_me
TOP_SECRET_ENV_VAR_2: change_me
TOP_SECRET_ENV_VAR_3: change_me
I've already tried to create some flow control using range to iterate, boolean variables and if statements, but go template seems to ignore my ifs after I change it to another value.
Now my secret template is like the one below.
{{ $flowcontrol := true -}}
{{ if $flowcontrol -}}
{{ range $env := $.Values.env -}}
{{ if eq $env.type "secret" -}}
apiVersion: v1
kind: Secret
metadata:
name: testsecret
data:
{{- range $env := $.Values.env }}
{{- if eq $env.type "secret" }}
{{ $env.name }}: "change_me"
{{- end }}
{{- end }}
{{ $flowcontrol := false }}
{{ end -}}
{{ end -}}
{{ end -}}
It results in three replicated secret.yaml files with 3 variables:
$ helm template .
---
# Source: teste/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: testsecret
data:
TOP_SECRET_ENV_VAR_1: "change_me"
TOP_SECRET_ENV_VAR_2: "change_me"
TOP_SECRET_ENV_VAR_3: "change_me"
apiVersion: v1
kind: Secret
metadata:
name: testsecret
data:
TOP_SECRET_ENV_VAR_1: "change_me"
TOP_SECRET_ENV_VAR_2: "change_me"
TOP_SECRET_ENV_VAR_3: "change_me"
apiVersion: v1
kind: Secret
metadata:
name: testsecret
data:
TOP_SECRET_ENV_VAR_1: "change_me"
TOP_SECRET_ENV_VAR_2: "change_me"
TOP_SECRET_ENV_VAR_3: "change_me"
How can one control the flow as: if the first one item of a list satisfies the condition, iterate through the rest of the same list only in another section after?
I've managed to create this feature! :D
Basicaly i've created a "template function" using define which iterates the env list from values.yaml and writes a string containing only the envs which the type property matches the word "secret".
Then I call that function using include and assign it's output to a variable.
If the variable length is greater than 0 (meaning it's not an empty string), the secret file is created and then I use the same string to fill the data property.
Here's the code containing the function and the secret template:
{{- define "get-secrets-from-env-list" -}}
{{- $allenv := index . 0 -}}
{{- range $i, $scrts := $allenv -}}
{{- if eq $scrts.type "secret" -}}
{{- nindent 0 $scrts.name -}}: {{ "change_me" | b64enc -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{ $secrets := include "get-secrets-from-env-list" (list .Values.env ) }}
{{- if gt (len $secrets) 0 -}}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ include "awesome-chart.fullname" $ }}
labels:
app.kubernetes.io/name: {{ include "awesome-chart.name" $ }}
helm.sh/chart: {{ include "awesome-chart.chart" $ }}
app.kubernetes.io/instance: {{ $.Release.Name }}
app.kubernetes.io/managed-by: {{ $.Release.Service }}
data:
{{- nindent 2 $secrets -}}
{{- end -}}%
Here's a sample output:
$ helm template .
---
# Source: awesome-chart/templates/secret.yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: my-app
labels:
app.kubernetes.io/name: awesome-chart
helm.sh/chart: awesome-chart
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Tiller
data:
TOP_SECRET_ENV_VAR_1: Y2hhbmdlX21l
TOP_SECRET_ENV_VAR_2: Y2hhbmdlX21l
TOP_SECRET_ENV_VAR_3: Y2hhbmdlX21l
Works like a charm! ;)
I want to generate a password in a Helm template, this is easy to do using the randAlphaNum function. However the password will be changed when the release is upgraded. Is there a way to check if a password was previously generated and then use the existing value? Something like this:
apiVersion: v1
kind: Secret
metadata:
name: db-details
data:
{{ if .Secrets.db-details.db-password }}
db-password: {{ .Secrets.db-details.db-password | b64enc }}
{{ else }}
db-password: {{ randAlphaNum 20 | b64enc }}
{{ end }}
You can build on shaunc's idea to use the lookup function to fix the original poster's code like this:
apiVersion: v1
kind: Secret
metadata:
name: db-details
data:
{{- if .Release.IsInstall }}
db-password: {{ randAlphaNum 20 | b64enc }}
{{ else }}
# `index` function is necessary because the property name contains a dash.
# Otherwise (...).data.db_password would have worked too.
db-password: {{ index (lookup "v1" "Secret" .Release.Namespace "db-details").data "db-password" }}
{{ end }}
Only creating the Secret when it doesn't yet exist won't work because Helm will delete objects that are no longer defined during the upgrade.
Using an annotation to keep the object around has the disadvantage that it will not be deleted when you delete the release with helm delete ....
I've got a lot of trouble with the answers from Jan Dubois and shaunc. So I built a combined solution.
The downside of Jan's answer: It leads to errors, when it is used with --dry-run.
The downside of shaunc's answer: It won't work, because the resources will be deleted on helm upgrade.
Here is my code:
# store the secret-name as var
# in my case, the name was very long and containing a lot of fields
# so it helps me a lot
{{- $secret_name := "your-secret-name" -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $secret_name }}
data:
# try to get the old secret
# keep in mind, that a dry-run only returns an empty map
{{- $old_sec := lookup "v1" "Secret" .Release.Namespace $secret_name }}
# check, if a secret is already set
{{- if or (not $old_sec) (not $old_sec.data) }}
# if not set, then generate a new password
db-password: {{ randAlphaNum 20 | b64enc }}
{{ else }}
# if set, then use the old value
db-password: {{ index $old_sec.data "db-password" }}
{{ end }}
It's still one of the biggest issues of Helm. As far as I understand no good solution is available yet (see https://github.com/helm/charts/issues/5167).
One dirty workaround is to create secret as pre-install hook. Obvious downside of this approach is that secret will not be deleted on helm delete.
apiVersion: v1
kind: Secret
metadata:
name: {{ template "helm-random-secret.fullname" . }}
annotations:
"helm.sh/hook": "pre-install"
"helm.sh/hook-delete-policy": "before-hook-creation"
labels:
app: {{ template "helm-random-secret.name" . }}
chart: {{ template "helm-random-secret.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
some-password: {{ default (randAlphaNum 10) .Values.somePassword | b64enc | quote }}
Building on shaunc's idea to use the lookup function, I've created the following template:
{{/*
Returns a secret if it already in Kubernetes, otherwise it creates
it randomly.
*/}}
{{- define "getOrGeneratePass" }}
{{- $len := (default 16 .Length) | int -}}
{{- $obj := (lookup "v1" .Kind .Namespace .Name).data -}}
{{- if $obj }}
{{- index $obj .Key -}}
{{- else if (eq (lower .Kind) "secret") -}}
{{- randAlphaNum $len | b64enc -}}
{{- else -}}
{{- randAlphaNum $len -}}
{{- end -}}
{{- end }}
Then you can simply configure secrets like:
---
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
PASSWORD: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "my-secret" "Key" "PASSWORD") }}"
You can use the lookup function and skip generation if secret already exists:
{{- if not (lookup "v1" "secret" .Release.Namespace "db-details") -}}
<create secret here>
{{- end -}}
The actual tools are all here. My workaround is just another combination of suggested tools
{{- if not (lookup "v1" "Secret" .Release.Namespace "mysecret") }}
apiVersion: v1
kind: Secret
metadata:
name: mysecret
annotations:
"helm.sh/resource-policy": "keep"
type: Opaque
stringData:
password: {{ randAlphaNum 24 }}
{{- end }}
So if there is no such secret, it will be created. If the secret is present, it will be removed from the chart, but not from the cluster, the "helm.sh/resource-policy": "keep" will prevent it.
You may ask (as someone already did above) why lookup, not .Release.IsUpdate. Imagine the situation: your secret is a password to a database. You keep the data in the persistent volume, the claim for which is also annotated by "helm.sh/resource-policy": "keep", so if you even uninstall and reinstall the chart, the data would persist. If you do so with .Release.IsUpdate as condition, then you password will be recreated, the old password will be lost and you will loose the access to your data. If you query for the secret existence, it won't happen.
I've rewritten kubernetes replicator and added some annotations to deal with this kind of problems: https://github.com/olli-ai/k8s-replicator#use-random-password-generated-by-an-helm-chart
Now can generate a random password with helm, and replicate it only once to another secret thus it won't be change by helm in the future.
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: admin-password-source
annotations:
k8s-replicator/replicate-to: "admin-password"
k8s-replicator/replicate-once: "true"
stringData:
password: {{ randAlphaNum 64 | quote }}
Hope it will help people.
You can leverage definitions in the _helpers.tpl
_helpers.tpl
{{/*
Create the secret name
*/}}
{{- define "mssql-server.secretName" -}}
{{- include "mssql-server.name" . }}-mssql-secret
{{- end }}
{{/*
Get sa password value
*/}}
{{- define "mssql-server.sapassword" -}}
{{- if .Release.IsInstall -}}
{{ .Values.sa_password | default (randAlphaNum 20) | b64enc | quote }}
{{- else -}}
{{ index (lookup "v1" "Secret" .Release.Namespace (include "mssql-server.secretName" .)).data "sa_password" }}
{{- end }}
{{- end }}
secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mssql-server.secretName" . }}
labels:
{{- include "mssql-server.labels" . | nindent 4 }}
type: Opaque
data:
sa_password: {{ include "mssql-server.sapassword" . }}
A bit late here, and most people may just catch it in the documentation:
helm does this for you with the annotation "helm.sh/resource-policy": keep
see:
https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource