3

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?

Vladimir Botka
  • 3,791
  • 6
  • 17
dutsnekcirf
  • 79
  • 1
  • 3
  • 14

2 Answers2

3

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.

Gerald Schneider
  • 19,757
  • 8
  • 52
  • 79
2

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 %}
Vladimir Botka
  • 3,791
  • 6
  • 17