I have Following go Template code
custom_listeners: {{ range $cl := $.Vars.CustomListeners }}
{{ $cl.ListenerType }}:
{{ range $k,$v := $cl.Values }}{{ $k }}: "{{ $v }}"
{{ end }}{{ end }}
Which creates the following YAML file . However the looping through the services add extra line between the service. I have tried different method to remove this line but it messes the yml formatting.
custom_listeners:
service1:
name: "service1"
port: "8091"
ssl_enabled: "false"
<-------------------------- Extra Line
service2:
name: "service2"
port: "8092"
ssl_enabled: "false"
<-------------------------- Extra Line
service3:
name: "service3"
port: "9093"
ssl_enabled: "false"
Just wondering what would be the best to way to get Desired result below :
custom_listeners:
service1:
name: "service1"
port: "8091"
ssl_enabled: "false"
service2:
name: "service2"
port: "8092"
ssl_enabled: "false"
service3:
name: "service3"
port: "7093"
ssl_enabled: "false"
https://pkg.go.dev/text/template#hdr-Text_and_spaces
... if an action's left delimiter (by default "{{") is followed
immediately by a minus sign and white space, all trailing white space
is trimmed from the immediately preceding text. Similarly, if the
right delimiter ("}}") is preceded by white space and a minus sign,
all leading white space is trimmed from the immediately following
text. ...
custom_listeners:
{{- range $cl := $.Vars.CustomListeners }}
{{ $cl.ListenerType }}:
{{- range $k,$v := $cl.Values }}
{{ $k }}: "{{ $v }}"
{{- end }}
{{- end }}
https://go.dev/play/p/aZ7tNqV2Phq
Related
I have a simple api that I've been deploying to K8s as a NodePort service via Helm. I'm working to add an ingress to the Helm chart but I'm having some difficulty getting the variables correct
values.yaml
ingress:
metadata:
name: {}
labels: {}
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: "testapi.local.dev"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: {}
port:
number: 80
templates/ingress.yaml, showing only the spec section where I'm having issues.
spec:
rules:
{{- range .Values.ingress.spec.rules }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path | quote }}
pathType: {{ .pathType | quote }}
backend:
service:
name: {{ include "testapi.service.name" . }}
port:
{{- range $key, $value := (include "testapi.deployment.ports" . | fromYaml) }}
number: {{ .port }}
{{- end}}
{{- end}}
{{- end}}
When running helm template it just leaves these values blank and I'm not sure where the syntax is wrong. Removing the {{- range .paths }} and the following .path and .pathType and replacing them with the value corrects the issue
spec:
rules:
- host: "testapi.local.dev"
http:
paths:
Comments revealed I should be using {{- range .http.paths }}.
Consider the following template:
...
{{- range .Values.additionalMetrics }}
- interval: 1m
port: {{ .name }}
{{- end }}
...
And the following values:
additionalMetrics:
- name: kamon-metrics
port: 9096
targetPort: 9096
If additionalMetrics is missing, the helm template will fail.
Is it possible to check first if additionalMetrics is defined, and then the range the values or continue otherwise?
Note: without making first if and then range, but in one condition, for example this is a not my desired solution:
{{- if .Values.additionalMetrics }}
{{- range .Values.additionalMetrics }}
- name: {{ .name }}
port: {{ .port }}
targetPort: {{ .targetPort }}
{{- end }}
{{- end }}
Thanks in advvance
The solution, which is not desired by you, is alright imo. It's simple and does what it's supposed to do. There is no need to make things complicated.
You could make it a bit more pretty with a with clause:
{{- with .Values.additionalMetrics }}
{{- range . }}
- name: {{ .name }}
port: {{ .port }}
targetPort: {{ .targetPort }}
{{- end }}
{{- end }}
If you really want to do it in a single statement, you could use as default an empty list:
{{- range .Values.additionalMetrics | default list }}
- name: {{ .name }}
port: {{ .port }}
targetPort: {{ .targetPort }}
{{- end }}
Hi we have listenerProcessor array in values.yaml like below
listenerProcessor:
- name: "nbi-sbi"
port: 99
allowedPath:
- "/sbi/test"
- "/sbi/test123"
for this we need to merge our predefined set of listenerProcessor array which is not exposed through values.yaml , which something like this below
listenerProcessor:
- name: nbinorc
port: 9910
allowedPath:
- /nbi/norc/tmf641/v4/orders/
- /nbi/norc/tmf645/v4/serviceQualification
- name: nbiuiv
port: 9920
allowedPath:
- /nbi/uiv/tmf640/v4/services/
so to merge this two as single property and iterate it over , defined a variable in _helper.tpl
{{- define "nifi.listener-processor.spec" -}}
listenerProcessor:
- name: nbinorc
port: 9910
allowedPath:
- /nbi/norc/tmf641/v4/orders/
- /nbi/norc/tmf645/v4/serviceQualification
- name: nbiuiv
port: 9920
allowedPath:
- /nbi/uiv/tmf640/v4/services/
{{ toYaml .Values.listenerProcessor.ports }}
{{- end -}}
Now i need to iterate it over .name & .port in NOTES.txt but its giving error " <.name>: can't evaluate field name in type interface {}".
In NOTES.txt i have this
{{- $listenerProcessor := (include "nifi.listener-processor.spec" . ) }}
{{- range $index, $element := fromYaml $listenerProcessor }}
{{ .name }}
{{ .port }}
{{- end }}
when i just print $element & $index those values its showing below output
NOTES:
$NIFI_CONF_DIRECTORY_PATH/extensions directory"
[map[allowedPath:[/nbi/norc/tmf641/v4/orders/ /nbi/norc/tmf645/v4/serviceQualification] name:nbinorc port:9910] map[allowedPath:[/nbi/uiv/tmf640/v4/services/] name:nbiuiv port:9920] map[allowedPath:[/sbi/test /sbi/test123] name:nbi-sbi port:99]]
listenerProcessor
Got that Working with below code, we need to have another variable and than do pass it for range over
{{- $listenerProcessor := (include "nifi.listener-processor.spec" . ) }}
{{- $test := fromYaml $listenerProcessor }}
{{- range $index, $element := $test.listenerProcessor }}
{{ .name }} {{ .port }} {{ .allowedPath }}
{{- end }}
Is it possible to filter the results of a range in helm?
For example the values file contains a list of maps like:
clients:
- name: clientA
id: id001
user: usernameA
pass: passwordA
- name: clientB
id: id002
user: usernameB
pass: passwordB
- name: clientA
id: id003
user: usernameA
pass: passwordA
In my template I need to extract only the unique values of user and pass.
I have something like this:
{{- range .Values.clients }}
- name: {{ .name | printf "user_%s" }}
valueFrom:
secretKeyRef:
name: somesecret
key: {{ .user | quote }}
- name: {{ .name | printf "pass_%s" }}
valueFrom:
secretKeyRef:
name: somesecret
key: {{ .pass | quote }}
{{- end }}
The end result should be something like:
- name: user_clientA
valueFrom:
secretKeyRef:
name: somesecret
key: usernameA
- name: pass_clientA
valueFrom:
secretKeyRef:
name: somesecret
key: passwordA
- name: user_clientB
valueFrom:
secretKeyRef:
name: somesecret
key: usernameB
- name: pass_clientB
valueFrom:
secretKeyRef:
name: somesecret
key: passwordB
I tried {{- range .Values.clients | pick "user" "pass" | uniq }} but doesn't seem to work. I don't know how to write this so i can filter just "user" and "pass" keys and discard the duplicates.
Even with its extensions, Helm doesn't support deep filtering of lists like you propose. There's not a great way to say "filter this list to the unique values of this map item", or "given an item, is it the first one in the list with some property". In a more functional language I could imagine using constructs like map or filter here, and Helm has no equivalents to these.
In practice the thing I'd be most likely to do is to declare this an invalid configuration. It would be lazy, but not unreasonable, to just execute the template as you've shown it. (If this is in a pod spec's env: block, I believe this would produce a valid object and the last setting of each environment variable takes effect.)
It's possible to do this checking in Helm templates; but it's a lot of code, in an unfamiliar language, that's hard to test. Also consider whether something like an operator could be a better implementation choice for you, since this can be written in a more standard language.
If you believe the length of the list will be reasonably short, you can write a recursive template to process this. It would receive the list of remaining items and the list of names it's already seen. If the list of items is empty, stop; if the current item has been seen, call ourselves with the rest of the list and the same seen list; otherwise emit the item, add it to the "seen" list, and call ourselves. In Python pseudocode this could look like:
def helper(remaining, seen):
if len(remaining) == 0:
# do nothing if the list is empty
return
else:
first = remaining[0]
name = first['name']
rest = remaining[1:]
if name in seen:
# we have already seen this item; skip it and go to the next one
return helper(rest, seen)
else:
# this is a new item; emit it and remember its name
emit(first)
new_seen = seen + [name]
return helper(rest, new_seen)
def process(the_list):
helper(the_list, [])
You can translate that logic to Helm templates, with the additional trick of packing the multiple parameters into a single list, since the Go text/template templates only take a single parameter. (If you need the top-level Helm object for .Values, .Release, .Files, etc. you need to pass that explicitly as well.)
{{- define "helper" -}}
{{- $remaining = index . 0 -}}
{{- $seen := index . 1 -}}
{{- if empty $remaining -}}
{{-/* do nothing */-}}
{{- else -}}
{{- $first := first $remaining -}}
{{- $name := $first.name -}}
{{- $rest := rest $remaining -}}
{{- if has $name $seen -}}
{{-/* recurse without emitting anything */-}}
{{- template "helper" (list $rest $seen) -}}
{{- else -}}
{{-/* emit the item */-}}
- name: user_{{ $first.name }}
valueFrom:
secretKeyRef:
name: somesecret
key: {{ quote $first.user }}
- name: pass_{{ $first.name }}
valueFrom:
secretKeyRef:
name: somesecret
key: {{ quote $first.pass }}
{{/* ...then move on to the next item */-}}
{{- $new_seen := append $seen $first.name -}}
{{- template "helper" (list $first $new_seen) -}}
{{- end -}}
{{- end -}}
{{- end -}}
env:
{{ include "helper" (list .Values.clients list) | indent 2 }}
I have an vendor Ansible playbook given to us and I will need add an new line to the j2 template and tweak the playbook for our env,
I will need to edit the template with an line -> retention_days: {{ xyz }}
This is how the orginal template looks like:
#cat cluster.j2
apiVersion: v1
metadata:
name: cluster
cluster_name: {{ my_name }}
data:
new_image: |+
baseImage: {{ FROM_repo }}
And here is my Ansible playbook to add the line.
---
- name: mydata
hosts: localhost
tasks:
- name: edit files
lineinfile:
dest: cluster.j2
line: " retention_days: {{ xyz }}"
insertafter: 'new_image'
My end result ie; my j2 template file should have the exact string like this
retention_days: {{ xyz }}
final - file should look like this ->
#cat cluster.j2
apiVersion: v1
metadata:
name: cluster
cluster_name: {{ my_name }}
data:
new_image: |+
retention_days: {{ xyz }}
baseImage: {{ FROM_repo }}
I don't want the {{ xyz }} to be treated as an variable by Ansible instead consider it a string and add them there... How can I escape the {{ and }} Please let me know .
Now ., I get an error: xyz is undefined..
MSG:
***The task includes an option with an undefined variable. The error was: 'xyz' is undefined***
As specified in the documentation, you can use {% raw %} to escape elements in a block, or add additional curly braces.
For example:
---
- name: mydata
hosts: localhost
tasks:
- name: edit files
lineinfile:
dest: cluster.j2
line: " retention_days: {% raw %}{{ xyz }}{% endraw %}"
# or
# line: " retention_days: {{ '{{ xyz }}' }}"
insertafter: 'new_image'