Helm include formatted map from values.yml - kubernetes-helm

I have a simple values.yml file for my Helm chart:
DbMigration:
Resources:
requests:
memory: 256Mi
limits:
memory: 512Mi
In a definition for my database migration job, I have this:
spec:
activeDeadlineSeconds: 120
template:
spec:
restartPolicy: Never
containers:
- name: myMigrate
image: myRepo/myService:0.0.1
imagePullPolicy: Always
resources:
requests:
{{- range $key, $value := $.Values.DbMigration.Resources.requests }}
{{ $key }}: {{ $value }}
{{- end }}
limits:
{{- range $key, $value := $.Values.DbMigration.Resources.limits }}
{{ $key }}: {{ $value }}
{{- end }}
Is is there any way to simplify the resources area so that I can just include all the data from $.Values.DbMigration.Resources? What I have works, but there must be a more concise way. I tried using the toYaml function in a fashion similar to this:
{{- toYaml $.Values.DbMigration.Resources }}
However, that results in:
Error: UPGRADE FAILED: YAML parse error on myTemplate.yaml: error converting YAML to JSON: yaml: line 30: mapping values are not allowed in this context

If you want to render the block from values.yaml "as is" then toYaml is pretty much all you are going to need.
spec:
activeDeadlineSeconds: 120
template:
spec:
restartPolicy: Never
containers:
- name: myMigrate
image: myRepo/myService:0.0.1
imagePullPolicy: Always
resources:
{{- toYaml $.Values.DbMigration.Resources | nindent 12 }}
If you are still having convert to JSON error try to play with indentation a bit, it's all there is.

There are multiple ways to achieve this. One of them is, instead of using range you can simply change the values.yaml into
DbMigration: |
resources:
requests:
memory: 256Mi
limits:
memory: 512Mi
and then make a change in migration template as
resources:
{{- .Values.DbMigration | indent 12 }}
Please change the indentation accordingly.
Another method is to use range and toYaml
DbMigration:
Resources:
requests:
memory: 256Mi
limits:
memory: 512M
migration template
resources:
{{- range $key, $value := $.Values.DbMigration.Resources }}
{{ $key }}:
{{ toYaml $value }}
{{- end }}

Related

Passing values to include function inside range, using defaults with merge

Given this deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment
spec:
revisionHistoryLimit: 5
template:
spec:
containers:
{{- include "app.container" (merge .Values.app $) | nindent 8 }}
{{- include "ports" (merge .Values.app ) | nindent 8 }}
{{- range $k, $v := .Values.extraContainers }}
{{- $nameDict := dict "name" $k -}}
{{- include "app.container" (mustMergeOverwrite $.Values.app $nameDict $v) | nindent 8 }}
{{- include "ports" (merge $nameDict $v ) | nindent 8 }}
{{- end }}
This helpers file...
{{/* vim: set filetype=helm: */}}
{{/*
app container base
*/}}
{{- define "app.containerBase" -}}
- name: {{ .name | default "app" }}
image: {{ .image.name }}:{{ .image.tag }}
{{- if .command }}
command:
{{- range .command }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .args }}
args:
{{- range .args }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- range .envVars }}
- name: {{ .name }}
{{- with .value }}
value: {{ . | quote }}
{{- end }}
{{- with .valueFrom }}
valueFrom:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- if or .Values.configMaps .Values.secrets .Values.configMapRef }}
envFrom:
{{- if .Values.configMaps }}
- configMapRef:
name: "{{- include "app.fullname" $ }}"
{{- end }}
{{- range .Values.configMapRef }}
- configMapRef:
name: {{ . }}
{{- end }}
{{- range $name, $idk := $.Values.secrets }}
- secretRef:
name: "{{- include "app.fullname" $ }}-{{ $name }}"
{{- end }}
{{- end }}
{{- end -}}
{{/*
app container
*/}}
{{- define "app.container" -}}
{{- include "app.containerBase" . }}
resources:
limits:
cpu: {{ .resources.limits.cpu | default "100m" | quote }}
memory: {{ .resources.limits.memory | default "128Mi" | quote }}
{{- if .resources.limits.ephemeralStorage }}
ephemeral-storage: {{ .resources.limits.ephemeralStorage }}
{{- end }}
requests:
cpu: {{ .resources.requests.cpu | default "100m" | quote }}
memory: {{ .resources.requests.memory | default "128Mi" | quote }}
{{- if .resources.requests.ephemeralStorage }}
ephemeral-storage: {{ .resources.requests.ephemeralStorage }}
{{- end }}
securityContext:
runAsNonRoot: true
runAsUser: {{ .securityContext.runAsUser }}
runAsGroup: {{ .securityContext.runAsGroup }}
allowPrivilegeEscalation: false
{{- end -}}
{{/*
ports
*/}}
{{- define "ports" -}}
{{- if .port }}
ports:
- containerPort: {{ .port }}
protocol: {{ .protocol | default "tcp" | upper }}
{{- range .extraPorts}}
- containerPort: {{ required ".port is required." .port }}
{{- if .name }}
name: {{ .name }}
{{- end }}
{{- if .protocol }}
protocol: {{ .protocol | upper }}
{{- end }}
{{- end }}
{{- end }}
{{- end -}}
This values.yaml
extraContainers:
extra-container2:
image:
name: xxx.dkr.ecr.eu-west-1.amazonaws.com/foobar-two
tag: test
command:
- sleep
- 10
envVars:
- name: FOO
value: BAR
probes:
readinessProbe:
path: /ContainerTwoReadinessPath
livenessProbe:
path: /ContainerTwoLivenessPath
resources:
limits:
cpu: 200Mi
memory: 2Gi
requests:
cpu: 200Mi
memory: 2Gi
extra-container3:
image:
name: xx.dkr.ecr.eu-west-1.amazonaws.com/foobar-three
tag: latest
command:
- sleep
- 10
envVars:
- name: FOO
value: BAZ
probes:
readinessProbe:
enabled: false
livenessProbe:
enabled: false
app:
image:
name: xx.dkr.ecr.eu-west-1.amazonaws.com/foobar
tag: latest
probes:
readinessProbe:
path: /_readiness
enabled: true
livenessProbe:
path: /_liveness
enabled: true
port: 100
resources:
limits:
cpu: 100Mi
memory: 1Gi
requests:
cpu: 100Mi
memory: 1Gi
Why does helm template result in the below:
---
# Source: corp-service/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment
spec:
revisionHistoryLimit: 5
template:
spec:
containers:
- name: app
image: xx.dkr.ecr.eu-west-1.amazonaws.com/foobar:latest
resources:
limits:
cpu: "100Mi"
memory: "1Gi"
requests:
cpu: "100Mi"
memory: "1Gi"
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
allowPrivilegeEscalation: false
ports:
- containerPort: 100
protocol: TCP
- name: extra-container2
image: xx.dkr.ecr.eu-west-1.amazonaws.com/foobar-two:test
command:
- "sleep"
- "10"
- name: FOO
value: "BAR"
resources:
limits:
cpu: "200Mi"
memory: "2Gi"
requests:
cpu: "200Mi"
memory: "2Gi"
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
allowPrivilegeEscalation: false
- name: extra-container3
image: xx.dkr.ecr.eu-west-1.amazonaws.com/foobar-three:latest
command:
- "sleep"
- "10"
- name: FOO
value: "BAZ"
resources:
limits:
cpu: "200Mi"
memory: "2Gi"
requests:
cpu: "200Mi"
memory: "2Gi"
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
allowPrivilegeEscalation: false
i.e why does extra-container3 have resources: set with values from extra-container2 instead of taking them from the default set of resources found under .values.app.
essentially, i want extracontainers to use default values from .values.app unless i explicitly set them inside the extracontainers section. however it seems that if a previous iteration of the loop over extracontainers defines some values, they are used in the next iteration if they are not overriden.
the resources for extra-container3 i am expecting are:
resources:
limits:
cpu: 100Mi
memory: 1Gi
requests:
cpu: 100Mi
memory: 1Gi
what am i doing wrong?
So there are a couple of things going on here, but mostly the answer is that (mergeMustOverwrite) mutates the $dest map, which causes your range to "remember" the last value it saw, which according to your question isn't the behavior you want. The simplest answer is to use (deepCopy $.Values.app) as the $dest, but there's an asterisk to that due to another bug we'll cover in a second
{{- $nameDict := dict "name" $k -}}
- {{- include "app.container" (mustMergeOverwrite $.Values.app $nameDict $v) | nindent 8 }}
+ {{- include "app.container" (mustMergeOverwrite (deepCopy $.Values.app) $nameDict $v) | nindent 8 }}
{{- include "ports" (merge $nameDict $v ) | nindent 8 }}
{{- end }}
You see, (deepCopy $.Values.app) stack-overflows helm because of your inexplicable use of (merge .Values.app $) drags in Chart, Files, Capabilities and the whole world. I'm guessing your _helpers.tpl must have been copy-pasted from somewhere else, which explains the erroneous relative .Values reference inside that define. One way to fix that is to remove the .Values.configMaps, so it will track the .app context like I expect you meant, or you can change the first (merge) to artificially create a "Values": {} item just to keep the template from blowing up when in tries to reference .app.Values.configMaps. The correct one will depend on what you were intending, but (merge .Values.app $) is almost certainly not it
so, either:
--- a/templates/_helpers.tpl
+++ b/templates/_helpers.tpl
## -28,13 +28,13 ## app container base
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
- {{- if or .Values.configMaps .Values.secrets .Values.configMapRef }}
+ {{- if or .configMaps .secrets .configMapRef }}
envFrom:
- {{- if .Values.configMaps }}
+ {{- if .configMaps }}
- configMapRef:
name: "{{- include "app.fullname" $ }}"
{{- end }}
- {{- range .Values.configMapRef }}
+ {{- range .configMapRef }}
- configMapRef:
name: {{ . }}
{{- end }}
or
containers:
- {{- include "app.container" (merge .Values.app $) | nindent 8 }}
+ {{- include "app.container" (merge .Values.app (dict "Values" (dict))) | nindent 8 }}
{{- include "ports" (merge .Values.app ) | nindent 8 }}

How to ammend a condition in helm charts

Currently, I am checking if lifecycle hooks are enabled, if yes add some extra delay:
{{- $delay := hasKey .Values "shutdownDelay" | ternary .Values.shutdownDelay 30 }}
{{- $graceperiod := hasKey .Values.service "terminationGracePeriodSeconds" | ternary .Values.service.terminationGracePeriodSeconds 120 }}
{{- $extraDelay := .Values.lifecycleHooks.enabled | ternary $delay 0 }}
terminationGracePeriodSeconds: {{ add $graceperiod $extraDelay }}
I want to cover a use case where the if .Values.lifecycleHooks.postStart and .Values.lifecycleHooks.prestart have some values then it should not add the extra delay in terminationGracePeriodSeconds
The values.yaml looks like
#shutdownDelay: 40
lifecycleHooks:
enabled: true
postStart:
exec:
command:
- echo
- "Run after starting container"
preStop:
exec:
command:
- echo
- "Run before stopping container"
service:
terminationGracePeriodSeconds: 120
So if the poststop hook value is defined like in values.yaml then it should not add any delay to terminationperiod.
The question is not very specific but if you are looking for "if"
condition with "and"/"or", below is an example might be helpfull.
As per your explanation assuming value for poststart/prestart, If the lifecycle.poststart is "false" and lifecycle.prestart is "true" then the terminationgraceperiodseconds wont have extradelay else condition will have an extradelay
{{- if and (eq .Values.lifecycleHooks.postStart "false") (eq .Values.lifecycleHooks.prestart "true")) }}
terminationGracePeriodSeconds: {{ $graceperiod }}
{{- else}}
terminationGracePeriodSeconds: {{ add $graceperiod $extraDelay }}
{{- end }}
conditional or
{{- if or (eq .Values.lifecycleHooks.postStart "false") (eq .Values.lifecycleHooks.prestart "true")) }}
terminationGracePeriodSeconds: {{ add $graceperiod $extraDelay }}
{{- end }}

if condition with AND operator and greater than condition in helm

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

Where do I put my NodePort Spec in my deployment.yaml

I am a total newbe with Helm charts, but I have managed to get a pod with with ApacheDS (LDAP server) running on it. I can exec shell into it and I can login and get responses from the LDAP server.
However, from outside the cluster, I get a connection refused. Looking this up, I "think" I need a NodePort: Kube documentation However, I cannot see where to put that spec. I have tried many things but just cant get it. According to the documentation I need something like this:
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 10389
targetPort: 10389
nodePort: 30007
Here is my deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "buildchart.fullname" . }}
labels:
{{- include "buildchart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "buildchart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "buildchart.selectorLabels" . | nindent 8 }}
spec:
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
- name: {{ .Values.imagePullSecrets }}
{{- end }}
serviceAccountName: {{ include "buildchart.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: admin-port
containerPort: 8080
hostPort: 8080
protocol: TCP
- name: ldap-port
containerPort: 10389
hostPort: 10389
protocol: UDP
livenessProbe:
exec:
command:
- curl ldap://localhost:10389/
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
readinessProbe:
exec:
command:
- sh
- -c
- curl ldap://localhost:10389/
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
How do I open this port to the rest of the world? Or at least the box the container is on.
Yes, you need to create a Service for your deployment.
Also, I suggest you do it without hardcoding, because easier to change a value in the values.yaml file than edit templates files for adding a new hardcode values.
In the deployment.yaml set:
...
{{ if .Values.ports }}
ports:
{{ range .Values.ports }}
- name: {{ .name }}
containerPort: {{ .containerPort }}
protocol: {{ .protocol }}
{{ end }}
{{ end }}
...
In the values.yaml set:
ports:
- name: admin-port
containerPort: 8080
nodePort: 8080
protocol: TCP
- name: ldap-port
containerPort: 10389
nodePort: 10389
protocol: UDP
Create service.yaml file and set
apiVersion: v1
kind: Service
metadata:
name: {{ include "buildchart.fullname" . }}
labels:
{{- include "buildchart.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
{{ range .Values.ports }}
- port: {{ .nodePort }}
targetPort: {{ .containerPort }}
protocol: {{ .protocol }}
name: {{ .name }}
{{ end }}
selector:
{{- include "buildchart.selectorLabels" . | nindent 4 }}
You should be adding that as a Service object spec not in your deployment object.
For example,
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 10389
targetPort: 10389
nodePort: 30007

How to merge 2 lists together (of containers) in helm?

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.