Score:3

Iterating through nested list / dictionary using Jinja2 templates

ph flag

I'm trying to dynamically configure multiple NFS servers in my system by generating their /etc/exports files using Ansible. I'm hoping to be able to do this with a jinja2 template. It's the jinja2 template that I can't figure out based on my exports list.

I have the following variables defined in my nfs role:

site_nfs_servers: ['ansibletarget1', 'ansibletarget2']

exports:
  - server: "ansibletarget1"
    shares:
      - path: "/my/first/share/path"
        client: "*"
        options: "rw,sync"
      - path: "/my/second/share/path"
        client: "*"
        options: "rw,sync,root_squash"
  - server: "ansibletarget2"
    shares:
      - path: "/another/shared/path/different/server"
        client: "*"
        options: "ro,sync"

I then have the following ansible play to generate the template:

- name: Generate the exports file.
  template:
    src: exports.j2
    dest: /etc/exports
    owner: root
    group: root
    mode: '0750'

My template currently looks like this:

{% for export in exports %}
{% if ansible_hostname in export.server %}
{% for share in shares %}
{{ share.path }} {{ share.client }} {{ share.options }}
{% endfor %}
{% endif %}
{% endfor %}

I don't think I'm anywhere near close to having the correct template structure. How on earth does one iterate through this list?

Score:3
in flag

You are missing the reference to the export in your second loop.

{% for export in exports %}
{% if ansible_hostname in export.server %}
{% for share in export.shares %}
{{ share.path }} {{ share.client }} {{ share.options }}
{% endfor %}
{% endif %}
{% endfor %}

It would however be better to define the shares in host variables, as shown in the answer by Vladimir.

Score:2
br flag

Create inventory

shell> cat hosts
[site_nfs_servers]
ansibletarget1
ansibletarget2

and put the shares into the host_vars

shell> cat host_vars/ansibletarget1.yml 
shares:
  - path: "/my/first/share/path"
    client: "*"
    options: "rw,sync"
  - path: "/my/second/share/path"
    client: "*"
    options: "rw,sync,root_squash"
shell> cat host_vars/ansibletarget2.yml 
shares:
  - path: "/another/shared/path/different/server"
    client: "*"
    options: "ro,sync"

Create a simplified role for testing

shell> tree roles/my_nfs_role/
roles/my_nfs_role/
├── tasks
│   └── main.yml
└── templates
    └── exports.j2

2 directories, 2 files
shell> cat roles/my_nfs_role/tasks/main.yml 
- template:
    src: exports.j2
    dest: /etc/exports.test
shell> cat roles/my_nfs_role/templates/exports.j2 
{% for share in shares %}
{{ share.path }} {{ share.client }} {{ share.options }}
{% endfor %}

Then, use the inventory group and the role in a playbook

shell> cat playbook.yml
- hosts: site_nfs_servers
  roles:
    - my_nfs_role

Run the playbook and create the files

shell> ansible-playbook -i hosts playbook.yml

PLAY [site_nfs_servers] ************************************************

TASK [my_nfs_role : template] ******************************************
changed: [ansibletarget1]
changed: [ansibletarget2]
 ...
shell> ssh admin@ansibletarget1 cat /etc/exports.test
/my/first/share/path * rw,sync
/my/second/share/path * rw,sync,root_squash

shell> ssh admin@ansibletarget2 cat /etc/exports.test
/another/shared/path/different/server * ro,sync

See Sample Ansible setup.


If you want to keep the shares in one object put the list into the groups_vars. To simplify the code, convert the list to a dictionary. You can use community.general.groupby_as_dict for example

shell> cat group_vars/all.yml
exports:
  - server: "ansibletarget1"
    shares:
      - path: "/my/first/share/path"
        client: "*"
        options: "rw,sync"
      - path: "/my/second/share/path"
        client: "*"
        options: "rw,sync,root_squash"
  - server: "ansibletarget2"
    shares:
      - path: "/another/shared/path/different/server"
        client: "*"
        options: "ro,sync"

exports_dict: "{{ exports|community.general.groupby_as_dict('server') }}"

gives

  exports_dict:
    ansibletarget1:
      server: ansibletarget1
      shares:
      - client: '*'
        options: rw,sync
        path: /my/first/share/path
      - client: '*'
        options: rw,sync,root_squash
        path: /my/second/share/path
    ansibletarget2:
      server: ansibletarget2
      shares:
      - client: '*'
        options: ro,sync
        path: /another/shared/path/different/server

Then modify the template. This should create the same files as before.

shell> cat roles/my_nfs_role/templates/exports.j2 
{% for share in exports_dict[inventory_hostname]['shares'] %}
{{ share.path }} {{ share.client }} {{ share.options }}
{% endfor %}
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.