Controlling indentation in Jinja2 template with Ansible - ansible

I have this playbook:
(venv) bash-3.2$ cat playbooks/nginx_config_generate.yml
---
- name: Generate Nginx Config
hosts: all
gather_facts: False
roles:
- ../roles/nginx_config_generate
Here is the files that make up the role:
(venv) bash-3.2$ cat roles/nginx_config_generate/tasks/main.yml
---
- name: Generate Nginx Config
import_tasks: gen_config.yml
(venv) bash-3.2$ cat roles/nginx_config_generate/tasks/gen_config.yml
- name: Generate Nginx Config File From Template
ansible.builtin.template:
src: sp.conf.j2
dest: "/tmp/nginx.cfg"
(venv) bash-3.2$ cat roles/nginx_config_generate/templates/sp.conf.j2
something here
# Upstreams
{% for ups in my_vars.upstreams %}
{{ ups.entry }}
{% endfor %}
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
{% for ups in my_vars.http_locations %}
{{ ups.entry }}
{% endfor %}
}
Here are my host_vars ...
(venv) bash-3.2$ cat inventory/prod/sfo/host_vars/127.0.0.1
my_vars_str: "host,127.0.0.1,srv1,srv2"
my_vars:
upstreams:
- entry: |
upstream something {
foo: blah blah
}
- entry: |
upstream completely {
foo: blah blah
different: True
}
http_locations:
- entry: |
location / {
set something
}
- entry: |
location /foo {
set something
hover: craft
}
When I run my play it produces this file:
(venv) bash-3.2$ cat /tmp/nginx.cfg
something here
# Upstreams
upstream something {
foo: blah blah
}
upstream completely {
foo: blah blah
different: True
}
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
location / {
set something
}
location /foo {
set something
hover: craft
}
}
As you can see the above is probably a valid nginx config but how can I fix up the indentation on my location lines to make it more readable?
UPDATE: I tried Carlos's suggestion so I have tried this:
...
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
{% for loc in my_vars.http_locations %}
{{ loc.entry }}
{% endfor %}
}
But that yields the same output:
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
location / {
set something
}
location /foo {
set something
hover: craft
}

Ok. I am started playing with the indent filter in my template. Here is the template I am using to generate the output I wanted:
something here
# Upstreams
{% for ups in my_vars.upstreams %}
{{ ups.entry }}
{% endfor %}
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
{% for loc in my_vars.http_locations %}
{{ loc.entry | indent(4)}}
{% endfor %}
}
Now I get this output:
something here
# Upstreams
upstream something {
foo: blah blah
}
upstream completely {
foo: blah blah
different: True
}
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
location / {
set something
}
location /foo {
set something
hover: craft
}
}

have you tried modifying the indentation in the second loop?
# HTTP Insecure
server {
blha blah blah
something here = that
this = that
{% for ups in my_vars.http_locations %}
{{ ups.entry }}
{% endfor %}
}

Related

I can't get Selectaddr to display attributes from my dict

We're running ansible 2.9.27 on Red Hat Linux 7.9. In my playbook, I have the following code:
- name: DEBUG THIS THING1
debug:
msg: "FOUND HOME: {{ found_service_accounts['svclinux']['home'] }}"
- name: DEBUG THIS THING2
debug:
msg: "FOUND: {{ found_service_accounts['svclinux']['home'] | selectattr('path', 'defined') | list }}"
The output looks like this:
TASK [service_accounts : DEBUG THIS THING1] ***************************************************************************
ok: [myhost.example.com] => {
"msg": "FOUND HOME: {u'owner': u'svclinux', u'path': u'/home/svclinux', u'group': u'svc-linux', u'permissions': u'755'}"
}
TASK [service_accounts : DEBUG THIS THING2] ***************************************************************************
ok: [myhost.example.com] => {
"msg": "FOUND: []"
}
I can't figure out why Ansible isn't printing the path attribute of my home. It just prints the empty list. Can you see my obvious mistake?
Thanks.
Note: found_service_accounts is a dict that was set as follows. The homes.results...stdout_lines[n] are simple strings gleaned from a shell command:
- name: set found_service_accounts
set_fact:
found_service_accounts: |
{% set j2fsa = { } %}
{%- for user in service_account_names -%}
{% set _ = j2fsa.update(
{ user : { 'home' : { 'path' : homes.results[loop.index0].stdout_lines[0],
'owner' : homes.results[loop.index0].stdout_lines[1],
'group' : homes.results[loop.index0].stdout_lines[2],
'permissions' : homes.results[loop.index0].stdout_lines[3]
},
'ssh_subdirectory' : {
'owner' : ssh_subdir.results[loop.index0].stdout_lines[0],
'permissions' : ssh_subdir.results[loop.index0].stdout_lines[1]
}
}
})
%}
{%- endfor %}
{{ j2fsa }}
and service_account_names is just a list like: [ 'svclinux' ]
found_service_accounts['svclinux']['home'] is a dictionary, not a list. You should directly access the value of path:
- name: DEBUG THIS THING2
debug:
msg: "FOUND: {{ found_service_accounts['svclinux']['home']['path'] }}"
What happens in your original attempt is that iterating over a dictionary returns just the dictionary keys (["owner", "path", "group", "permissions"]), and strings do not have an attribute named path.

Stuck with a looping situation using jinja template with Ansible

I am stuck with a looping situation using jinja template with Ansible.
My jinja template:
{% for int in interfaces | difference(existing_conf) %}
interface {{ int }}
{% for ip in dhcp_servers | difference(existing_conf) %}
ip dhcp relay address {{ ip }}
{% endfor %}
{% endfor %}
TASK [view the existing config] *******************************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1] => {
"existing_conf": [
"Vlan1",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/49",
"10.1.1.2",
"Ethernet1/50",
"10.1.1.2"
]
}
TASK [Needed config] *****************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1]
TASK [View the needed config] ************************************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1] => {
"needed_conf": [
"Vlan1",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/49",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/50",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4"
]
}
My variables:
interfaces=["Vlan1", "Ethernet1/49", "Ethernet1/50"]
dhcp_servers=["10.1.1.2", "10.1.1.3", "10.1.1.4"]
With my template, I am getting an empty config.
Config I want:
interface Ethernet1/49
ip dhcp relay address 10.1.1.3
ip dhcp relay address 10.1.1.4
interface Ethernet1/50
ip dhcp relay address 10.1.1.3
ip dhcp relay address 10.1.1.4
Note: I only want to add the config which is not in the running-config of switch.
Q: "... I am getting an empty config."
A: There are no items to iterate over. The difference
- debug:
msg: "{{ interfaces|difference(existing_conf) }}"
gives an empty list
msg: []

How to correctly define subelements in Ansible jinja2 template(with subelements)?

I'm writing ansible-playbook to divide nginx.conf with includes. In my opinion it would be more comfortable to use nginx.conf with such option cause I can include or exclude some config block at playbook vars.
At current time I have trouble with part: name: 2. Copy nginx.conf config.
playbook.yml:
- name: "setup_nginx"
hosts: "TEST_HOST"
gather_facts: yes
remote_user: root
vars:
nginx_worker_processes: "{{ ansible_processor_cores }}"
nginx_worker_connections: "32768"
nginx_worker_rlimit_nofile: "{{ (ansible_processor_cores*{{ nginx_worker_connections }}*2)|int|abs }}"
nginx_directories:
- directory: inc
nginx_files:
- file: "gzip.inc"
- file: "logs.inc"
- file: "mime.types"
- file: "tuning.inc"
- file: "proxy.inc"
- file: "ssl.inc"
- directory: sites
nginx_files:
- file: "mysite1"
- file: "mysite2"
- tasks:
- name: 1. Create nginx directories
file:
path: "/etc/nginx/{{ item.directory }}"
state: directory
owner: nginx
group: nginx
with_items:
- "{{ nginx_directories }}"
- name: 2. Copy nginx.conf config.
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: 0640
owner: nginx
group: nginx
backup: yes
with_subelements:
- "{{ nginx_directories }}"
- nginx_files
nginx.conf.j2:
user nginx;
worker_processes {{ ansible_processor_cores }};
events {
worker_connections 32768;
use epoll;
multi_accept on;
}
worker_rlimit_nofile {{ (ansible_processor_cores*32768*2)|int|abs }};
http {
{% for val in nginx_directories %}
include /etc/nginx/{{ item.0.directory }}/{{ item.1.file }};
{% endfor %}
}
I expect result:
user nginx;
worker_processes 1;
events {
worker_connections 32768;
use epoll;
multi_accept on;
}
worker_rlimit_nofile 65536;
http {
include /etc/nginx/inc/gzip.inc;
include /etc/nginx/inc/logs.inc;
include /etc/nginx/inc/mime.types;
include /etc/nginx/inc/tuning.inc;
include /etc/nginx/inc/proxy.inc;
include /etc/nginx/inc/ssl.inc;
include /etc/nginx/sites/mysite1;
include /etc/nginx/sites/mysite2;
}
But actual result:
user nginx;
worker_processes 1;
events {
worker_connections 32768;
use epoll;
multi_accept on;
}
worker_rlimit_nofile 65536;
http {
include /etc/nginx/sites/mysite2.j2;
include /etc/nginx/sites/mysite2.j2;
}
I think the trouble is that I'm not correctly define subelements at template nginx.conf.j2.
Regards
Remove the loop in you template task and in your template use
{% for val in nginx_directories | subelements('nginx_files') %}
include /etc/nginx/{{ val.0.directory }}/{{ val.1.file }};
{% endfor %}
FWIW. There is an elegant solution with config_encoder_filters which encodes YAML data
my_nginx_vhost_config:
- server:
- listen 8080
- server_name www.example.com
- "location /":
- root /usr/local/www/nginx-dist/
- index index.html
with simple template
# {{ ansible_managed }}
{{ my_nginx_vhost_config | encode_nginx }}
to nginx configuration
# cat /usr/local/etc/nginx/conf.d/default.conf
# Ansible managed
server {
listen 8080;
server_name www.example.com;
location / {
root /usr/local/www/nginx-dist/;
index index.html;
}
}
Details are available in the nginx role.

Loop through Ansible variable in in .j2 file

I have one ansible var list defined in group_vars
member_list:
- a
- b
I have one proxy.j2 template
{% for var in members_list %}
server {
server_name {{ var }}-{{ server_name }};
{% endfor %}
How could I loop through that list to get the value in .j2 file?
You should be using {{ item }} instead of {{ var }}.

Is it possible to use and ansible-fact with the with_dict module?

I'm trying to write a role to configure a keepalived cluster. I was hoping to pass unique info into the a template based on the IP of the target box.
In this scenario: Server A is 192.168.1.140 and Server B is 192.182.1.141 and the VIP would be 192.168.1.142
the dictionary would look something like this:
---
192.168.1.140:
peer: 192.168.1.141
priority: 110
vip: 192.168.1.142
192.1.168.1.141
peer:192.168.1.140
priority: 100
vip: 192.168.1.142
I was hoping the task would look like this:
---
- name: keepalived template
template:
src: keepalived.j2
dest: /etc/keepalived/keepalived.conf
owner: root
group: root
mode: 0644
with_dict: '{{ ansible_default_ipv4.address }}'
and the template would look like this:
}
vrrp_instance VI_1 {
interface eth0
priority {{ item.value.priority }}
...
unicast_scr {{ ansible_default_ipv4.address }}
unicast_peer {
{{ item.value.peer }}
}
virtual_ipaddresses {
{{ item.value.vip }} }
}
Any insight would be greatly appreciated
John
Group your peers details under some common dictionary:
---
peer_configs:
192.168.1.140:
peer: 192.168.1.141
priority: 110
vip: 192.168.1.142
192.1.168.1.141
peer:192.168.1.140
priority: 100
vip: 192.168.1.142
with_... is generally for looping, you don't need any loop, as I see, so use:
- name: keepalived template
template:
src: keepalived.j2
dest: /etc/keepalived/keepalived.conf
owner: root
group: root
mode: 0644
vars:
peer_config: '{{ peer_configs[ansible_default_ipv4.address] }}'
and template:
vrrp_instance VI_1 {
interface eth0
priority {{ peer_config.priority }}
...
unicast_scr {{ ansible_default_ipv4.address }}
unicast_peer {
{{ peer_config.peer }}
}
virtual_ipaddresses {
{{ peer_config.vip }} }
}

Resources