How to read env property file in the config map - kubernetes

I have a property file in chart/properties folder. For example chart/properties/dev is the file
and the contents of it looks like the below
var1=somevalue1
var2=somevalue2
var3=somepwd=
var4=http://someurl.company.com
some of the value strings in property file have an =. There are also some empty lines in the property file.
and chart/configmap.yaml looks like below
apiVersion: v1
kind: ConfigMap
metadata:
name: env-configmap
namespace: {{ .Release.Namespace }}
data:
{{ range .Files.Lines "properties"/.Values.env.required.environment }}
{{ . | replace "=" ": " }}
{{ end }}
Generated yaml file:
---
# Source: app/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: env-configmap
namespace: default
data:
var1: somevalue1
var2: somevalue2
var3: somepwd:
var4: http://someurl.company.com
The generated output property entries are missing double quote in the value, as a result deployment complains of it when the value strings contain special characters.
I'm expecting the configmap.yaml data block to be a proper yaml (Indent 2) like file with the above changes. With above changes, there are extra lines after each property entry in yaml file. I got this to work partially when there are no blank lines and no value strings with =. Need help to get this working correctly.
Expected yaml file:
---
# Source: app/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: env-configmap
namespace: default
data:
var1: "somevalue1"
var2: "somevalue2"
var3: "somepwd="
var4: "http://someurl.company.com"

You can follow go template syntax to do that. I update config.yaml like following works
apiVersion: v1
kind: ConfigMap
metadata:
name: env-configmap
namespace: {{ .Release.Namespace }}
data:
{{ range .Files.Lines "properties"/.Values.env.required.environment }}
{{- if ne . "" -}}
{{- $parts := splitn "=" 2 . -}} # details about split function http://masterminds.github.io/sprig/string_slice.html
{{ $parts._0 }}: {{ $parts._1 | quote }}
{{end}}
{{ end }}

I could not comment on your question because of my reputation. If it is possible for your case, you can use the config map as a file. I think Reading the property file in your code is easier.
https://kubernetes.io/docs/concepts/configuration/configmap/#using-configmaps-as-files-from-a-pod

Related

Helm: render variable in a block inserted with toYaml

I have a variable block configuring triggers in a Keda scaled object like so:
# [...]
autoscaling:
enabled: true
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-server.prometheus.svc.cluster.local
metricName: prometheus_redis_message_count
query: avg(redis_queue_job_states_total{state=~"waiting|active", namespace="{{ $.Release.Namespace }}", queue="reports"}) without (instance, pod, pod_template_hash, state)
threshold: '1'
activationThreshold: '1'
# [...]
as you can see, autoscaling.triggers[0].metadata.query contains a variable ( .Release.Namespace ) which does not get rendered when using the toYaml function (which is understood).
Is there a way, maybe with some named template magic or so, to put all items in triggers into my rendered template while also rendering vars contained inside (like .Release.Namespace?)
Right now I have it like this (which does work for now so I can proceed), but obviously is not great because it's way to static (won't allow multiple triggers or even other trigger types in the future):
# [...]
- type: prometheus
metadata:
serverAddress: http://prometheus-server.prometheus.svc.cluster.local
metricName: prometheus_redis_message_count
{{- with index .autoscaling.triggers 0 }}
query: {{ tpl .metadata.query $ }}
{{- end }}
threshold: '1'
activationThreshold: '1'
# [...]
edit:
thanks to David's reply I was able to write it like so:
triggers:
{{- range .autoscaling.triggers }}
- type: {{ .type }}
metadata:
query: {{ tpl .metadata.query $ }}
serverAddress: {{ tpl .metadata.serverAddress $ }}
threshold: {{ tpl .metadata.threshold $ | quote }}
activationThreshold: {{ tpl .metadata.activationThreshold $ | quote}}
metricName: {{ tpl .metadata.metricName $ }}
{{- end }}
which makes it a valid array and also templates all the fields. Anyway, thanks for the help!
The quick-and-dirty answer is to just run tpl on the output of toYaml. toYaml takes an arbitrary structure and returns a string, but at that point it's just a string and there's nothing particularly special about it.
triggers:
{{ tpl (toYaml .Values.autoscaling.triggers) . | indent 2 }}
I'm not clear that anything more nuanced is possible without iterating through the entire list and running tpl on each individual structure.
triggers:
{{- range .Values.autoscaling.triggers }}
- type: {{ .type }}
metadata:
query: {{ tpl .metadata.query $ }}
{{- end }}
Neither the core Go templating language nor the Sprig extensions have any sort of generic "map" function that could operate on the values of a nested object while retaining its structure.

Is there a native way to access the scope inside a range from inside a function?

In my template I use a range with a param like this:
{{- range $key, $value := .Values.myParam }}
---
fieldOne: $value.fieldOne
fieldTwo: $value.fieldTwo
myFunc: {{- include "myFunc" $ }}
{{ end }}
I want myFunc to get access global values (.Values.global.*) AND the scoped variables like $value.fieldOne and $value.fieldTwo. Is this possible?
If I don't pass myFunc $ it won't have access to globals. If don't pass it $value it won't have access to the range scoped values.
And I'd like to maintain the standard .Values.global.* referencing syntax for accessing globals inside myFunc- I don't want to create my own special map object the function has to read differently.
Currently I am doing this:
myFunc: {{- include "myFunc" (merge $ $value) }}
Then in myFunc I can access this:
{{- define "myFunc" -}}
# Get globals in the usual way
{{- .Values.global.myGlobal }}
# Get my scoped values for the current iteration
{{- .fieldOne }}
{{- .fieldTwo }}
{{- end -}}
Is this the only way to do this? It seems to work reasonably well, but I want to confirm there isn't a builtin like ., but for range scoped variables I can use inside myFunc.
EDIT
I discovered an issue with my approach, it appears calling merge inside the function is mutating the root map.
For example doing this:
# helper function
{{- define "myFunc" -}}
# Get globals in the usual way
someGlobal: {{- .Values.global.myGlobal }}
# Get my scoped values for the current iteration
fieldOne: {{- .fieldOne }}
fieldTwo: {{- .fieldTwo }}
{{- end -}}
# In a template
{{- range $key, $value := .Values.myParam }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: $value.fieldOne
data:
{{- include "myFunc" (merge $ $value) }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: $value.fieldOne
data:
{{- include "myFunc" (merge $ $value) }}
{{ end }}
When I look at the outputted config maps the values returned from myFunc are the same. I also noticed I can do this:
# In a template
{{- range $key, $value := .Values.myParam }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: $value.fieldOne
data:
{{- include "myFunc" (merge $ $value) }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: $value.fieldOne
data:
someGlobal: {{ .Values.global.myGlobal }}
fieldOne: {{ $value.fieldOne }}
fieldTwo: {{ $value.fieldTwo }
{{ end }}
In the second ConfigMap the values are the same as the first. It's like calling merge inside myFunc is mutating the root map in place.
Ultimately I had to move the logic out of myFunc and call merge directly in the template to avoid this- doing that each iteration correctly gets its item's values.
A Go text/template template only takes one parameter. As with other programming languages, a function (template) can't access local variables in its caller, only things that are explicitly passed in parameters.
In short: the way you have it now is more or less as good as it's possible to do, given the constraints of the template language. If you want to directly reference .Values then the single template parameter must be a dictionary object and not something else.

How to Return Part of another YAML file in Helm Go Template

I have a Yaml file in a folder as abc.yaml and the content is
metadata:
test1: apple
test2: banana
test3: cat
container:
image: foo
text: xyz
variables:
ojb: one
meta: two
and I have another file values.yaml.j2 which needs part of the above content.
metadata:
test4: dog
test5: elephant
{{ .... Here I need test1, test2, test3 from the above (abc.yaml).... }}
container:
name: test
{{ .... Here I need image and text from the above (abc.yaml) ....}}
variables:
ping: pong
{{ ..... Here I need ojb and meta from the above (abc.yaml) .... }}
When I was exploring Helm go templates, I found, Files.Lines will return line by line. But I need specific lines as I mentioned above.
Any solution with go template wo get the part of other yaml file?
If you know the other file is YAML, Helm contains a lightly-documented fromYaml extension that can parse it.
{{- $abc := .Files.Get "abc.yaml" | fromYaml }}
From there you have a couple of options on how to proceed. One tool you have is the corresponding, also lightly-documented, toYaml extension that converts an arbitrary structure back to YAML.
So one choice is to directly emit the values you think you need:
metadata:
test4: dog
test5: elephant
test1: {{ $abc.metadata.test1 }}
test2: {{ $abc.metadata.test2 }}
test3: {{ $abc.metadata.test3 }}
A second is to emit the new values for each block, plus the existing content:
metadata:
test4: dog
test5: elephant
{{ $abc.metadata | toYaml | indent 2 -}}
A third is to modify the structure in-place, and then ask Helm to write out the whole thing as YAML. Unusual for Helm template functions, set modifies the dictionary it's given in place.
{{- $_ := $abc.metadata | set "test4" "dog" | set "test5" "elephant" -}}
{{- toYaml $abc -}}

helm multiline file inside range

Im trying to create one secret with multiple file in it.
My value.yaml ( the format of the multiline is not yaml or json)
secretFiles:
- name: helm-template-file
subPath: ".file1"
mode: "0644"
value: |
This is a multiline
value, aka heredoc.
Then my secret file template is secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: {{ include "helm-template.fullname" . }}-file
namespace: {{ .Release.Namespace }}
labels:
app: {{ include "helm-template.name" . }}
chart: {{ include "helm-template.chart" . }}
type: Opaque
stringData:
{{- range .Values.secretFiles }}
{{ .subPath}}: |
{{ .value | indent 4}}
{{- end }}
The helm install gives error "error converting YAML to JSON: yaml: line 12: did not find expected comment or line break". How can I fix it? Thank you.
values.yaml
secretFiles:
- name: helm-template-file
subPath: ".file1"
mode: "0644"
value: |
This is a multiline
value, aka heredoc.
- name: helm-template-file2
subPath: ".file2"
mode: "0644"
value: |
This is a multiline222
value, aka heredoc.
xxx_tpl.yaml
...
stringData:
{{- range .Values.secretFiles }}
{{ .subPath}}: |
{{- .value | nindent 4}}
{{- end }}
output
...
stringData:
.file1: |
This is a multiline
value, aka heredoc.
.file2: |
This is a multiline222
value, aka heredoc.
ref:
nindent
The nindent function is the same as the indent function, but
prepends a new line to the beginning of the string.
indent
The indent function indents every line in a given string to the specified indent width
When you indent a line, you need to make sure the markup in the line starts at the very first column.
stringData:
{{- range .Values.secretFiles }}
{{ .subPath}}: |
{{ .value | indent 4}}{{/* no space at start of this line */}}
{{- end }}
In the form you have in the original question, the four spaces at the start of the line are copied to the output, then the indent expression adds four more spaces to the start of every line. This results in the first line being indented by eight spaces, and the second line by 4, and that inconsistency results in the YAML parse error you see.
You'd also be able to see this visually if you run helm template --debug over your chart; you'll see the same error but also the template output that can't be parsed.
(#z.x's answer solves the same problem a different way: putting the - inside the curly braces removes both the leading whitespace and also the preceding newline, and then changing indent to nindent puts the newline back.)

Add suffix to each list member in helm template

I'm trying to iterate over a list in a helm template, and add a suffix to each member.
I currently have this block of code that does exactly that:
{{- range $host := .Values.ingress.hosts }}
{{- $subdomain := initial (initial (splitList "." $host)) | join "." }}
{{- $topLevelDomain := last (splitList "." $host) }}
{{- $secondLevelDomain := last (initial (splitList "." $host)) }}
- host: {{- printf " %s-%s.%s.%s" $subdomain $environment $secondLevelDomain $topLevelDomain | trimSuffix "-" }}
{{- end }}
Since I need to do the exact same manipulation twice in the same file, I want to create a new list, called $host-with-env, that will contain the suffix I'm looking for. That way I can only perform this operation once.
Problem is - I've no idea how to create an empty list in helm - so I can't append items from the existing list into the new one.
Any idea how can I achieve this?
I'm also fine with altering the existing list but every manipulation I apply to the list seems to apply to the scope of the foreach I apply to it.
Any ideas how to go about this?
It might not be quite clear what result are you trying to achieve, it will be helpful to add your input, like your values.yaml and the desired output. However, I added an example that answers your question.
Inspired by this answer, you can use dictionary.
This code will add suffix to all .Values.ingress.hosts and put them into $hostsWithEnv dictionary into a list, which can be accessed by myhosts key
values.yaml
ingress:
hosts:
- one
- two
configmap.yaml
{{- $hostsWithEnv := dict "myhosts" (list) -}}
{{- range $host := .Values.ingress.hosts -}}
{{- $var := printf "%s.domain.com" $host | append $hostsWithEnv.myhosts | set $hostsWithEnv "myhosts" -}}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
{{- range $hostsWithEnv.myhosts}}
- host: {{- printf " %s" . | trimSuffix "-" }}
{{- end }}
output
$ helm install --debug --dry-run .
[debug] Created tunnel using local port: '62742'
...
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
- host: one.domain.com
- host: two.domain.com