Jinja2 For Loop over a YAML List of Dictionaries - templates

I am fairly new to Jinja2 and I have an issue I can't seem to resolve no matter what I try. I am trying to create a config file for a device using a Jinja2 template and some variable files I have created. I cannot seem to get it working at all when I am specifying a list of dictionaries for my YAML variable file.
template:
{% for id in VLANS %}
vlan {{ id.id }}
name {{ id.name }}
vn-segment {{ id.vni }}
{% endfor %}
variable file:
VLANS:
- id: 9
name: "VLAN9"
vni: 109
- id: 10
name: "VLAN10"
vni: 110
- id: 11
name: "VLAN11"
vni: 111
- id: 12
name: "VLAN12"
vni: 112

Looks fine. The play below
- name: Template
template:
src: template.j2
dest: test.txt
gives
shell> cat test.txt
vlan 9
name VLAN9
vn-segment 109
vlan 10
name VLAN10
vn-segment 110
vlan 11
name VLAN11
vn-segment 111
vlan 12
name VLAN12
vn-segment 112
with template
shell> cat template.j2
{% for item in VLANS %}
vlan {{ item.id }}
name {{ item.name }}
vn-segment {{ item.vni }}
{% endfor %}

Related

I have a Jinja template that only prints the first match of a regex match but I would like it to print all matches

I run a playbook that executes a Cisco command show interfaces and I store that into a variable.
I then jump into a template for further processing to only find Ethernet interfaces and errors.
I find the first match of both interface and error which is fine but I do not find the second match of interface and error in the control environment. The lab device has two interfaces Ethernet0/0 and Ethernet0/1.
Can someone make a suggestion so that I can match on both interfaces and respective errors?
Here is the task:
Blockquote
tasks:
- name: gather interface stats and store in register
ios_command:
commands:
- show interface | section include is up
register: interface_errors
- name: store interface_errors_file
copy:
content: {{interface_errors}}
dest: ./{{inventory_hostname}}_interface_full.txt
- name: deploy template
copy:
content: "{{lookup('template', 'errors.j2') }}"
dest: ./{{inventory_hostname}}_errors.txt
Template:
Blockquote
{% for int in interface_errors.stdout %}
{% set int_name = int | regex_search('Ethernet\d{1,3}\/\d{1,3} is up') %}
{% if 'Ethernet' in int_name | string %}
- Interface: {{int_name}}
{% endif %}
{% endfor %}
{% for int_err in interface_errors.stdout %}
{% set int_errors = int_err | regex_search('\d{1,10000}.input.errors|output.errors') %}
{% if 'errors' in int_errors %}
- Interface Errors: {{int_errors}}
{% endif %}
{% endfor %}

Keep YAML list structure in Ansible templates

Say I have a variable file
---
app1:
environments:
- test
- demo
paths:
- /home/someuser/_env_/*.log
- /var/log/something/*.log
id: _env_
that's included in my playbook like this
---
- hosts: all
become: yes
vars:
apps:
- app1
vars_files:
- ~/vars/app1_vars.yml
tasks:
- name: Update config
template:
src: foo.j2
dest: /home/configurer/test.conf
owner: configurer
group: configurer
mode: '0644'
lstrip_blocks: yes
and the template itself is
{% for app in apps %}
{% for env in vars[app]['environments'] %}
- type: log
enabled: true
paths:
{% for path in vars[app]['paths'] %}
- {{ path | replace("_env_", env) }}
{% endfor %}
fields:
app_id: {{ vars[app]['id'] | replace("_env_", env) }}
{% endfor %}
{% endfor %}
That gives me as output
- type: log
enabled: true
paths:
- /home/someuser/test/*.log
- /var/log/something/*.log
fields:
app_id: test
- type: log
enabled: true
paths:
- /home/someuser/demo/*.log
- /var/log/something/*.log
fields:
app_id: demo
Is there a more compact and nicer way to iterate over the paths variable list and keep the YAML list structure with the -s?
After some experimenting I did the following as an alternative:
Changed the variable file:
---
app1:
environments:
- test
- demo
filebeat_properties:
paths:
- /home/someuser/_env_/*.log
- /var/log/something/*.log
fields:
app_id: _env_
Used to_nice_yaml to pretty-print the config:
{% for app in apps %}
{% for env in vars[app]['environments'] %}
- type: log
enabled: true
{{ vars[app]["filebeat_properties"] | to_nice_yaml(indent=2, width=9999) | indent(2) | replace("_env_", env) }}
{% endfor %}
{% endfor %}

Golang template (helm) iterating over a list of maps

I'm using helm to generate kubernetes yamls.
My values.yaml looks like this:
...
jobs:
- nme: job1
command: [sh, -c, "/app/deployment/start.sh job1"]
activeDeadlineSeconds: 600
- name: job2
command: [sh, -c, "/app/deployment/start.sh job2"]
activeDeadlineSeconds: 600
...
templates/jobs.yaml
{{ range $i, $job := .Values.jobs -}}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "name" . }}-{{ $job.name }}
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }}
template:
metadata:
labels:
app: {{ template "name" . }}-{{ $job.name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: {{ $job.command }}
env:
{{ toYaml .Values.service.env | indent 10 }}
ports:
- containerPort: {{ .Values.service.internalPort }}
{{- end }}
Helm is failing with this error:
Error: UPGRADE FAILED: render error in "app1/templates/jobs.yaml": template: app1/templates/_helpers.tpl:6:18: executing "name" at <.Chart.Name>: can't evaluate field Name in type interface {}
When I look at _helpers.tpl:
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
If I remove the range loop and references to $job in my jobs.yaml, the _helpers.tpl name template works fine. When I add in the loop, it fails.
It seems like within the loop, all dot . pipeline, which contains the scope for .Chart and .Values, is reassigned to something else.
What am I doing wrong?
Inside the loop the value of the . is set to the current element and you have to use $.Chart.Name to access your data.
I asked a similar question and I think the answer https://stackoverflow.com/a/44734585/8131948 will answer your question too.
I ended up saving the global context and then updating all of my references like this:
{{ $global := . }}
{{ range $i, $job := .Values.jobs -}}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "name" $global }}-{{ $job.name }}
labels:
chart: "{{ $global.Chart.Name }}-{{ $global.Chart.Version | replace "+" "_" }}"
spec:
activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }}
template:
metadata:
labels:
app: {{ template "name" $global }}-{{ $job.name }}
spec:
containers:
- name: {{ $global.Chart.Name }}
image: "{{ $global.Values.image.repository }}:{{ $global.Values.image.tag }}"
imagePullPolicy: {{ $global.Values.image.pullPolicy }}
command: {{ $job.command }}
env:
{{ toYaml $global.Values.service.env | indent 10 }}
ports:
- containerPort: {{ $global.Values.service.internalPort }}
{{- end }}

Consul-Template filter if not match

Is there a way to use consul template to filter if something is not a match?
Something like
{{services NOT "#east-aws"}}
...
I'm not finding it in the repository readme
Ok, figured it out.
{{ range services }}
{{ if ne .Name "name-of-service" }}
......
{{ end }}
{{ end }}

Golang pagination

I need to implement pagination. Actually I have pages array, page param and per_page variable.
In my code:
pages_count := math.Floor(float64(len(pages)) / float64(per_page))
then in template I need something like (pseudocode):
{{ if .page - 2 > 0 }}
{{ $start_page := .page - 2 }}
{{ else }}
{{ $start_page := 1 }}
{{ end }}
{{ if .page + 2 >= .pages_count }}
{{ $finish_page := .page + 2 }}
{{ else }}
{{ $finish_page := .pages_count }}
{{ end }}
<ul>
{{ for $i := $start_page; $i <= $finish_page; ++$i }}
<li {{ if $i == .page }} class="current_page" {{ end }}>
$i
</li>
{{ end }}
</ul>
How to implement this correctly?
Thx
When I work with Java templates (e.g. Velocity), I find that the kinds of template logic you are asking about lead to over-complex templates. The same applied in Go.
My solution is to move logic into the view-model layer and keep the templates rather dumb. This means that the controller and view model have to do a bit more work precomputing the kinds of values that your template shows. The view model is consequently larger - but it's just simple data and is easy to unit-test.
In your specific example, you would keep the for-loop that builds up the <li> list. Everything above the <ul> open tag can be handled in the view model. So the template would just work with some precomputed data.