How to Iterate Over Nested Dictionary with Lists in Ansible Playbooks for Permission Management


32 views

When working with Ansible, we often encounter complex data structures that need to be processed in playbooks. The example shows a common scenario where we have:

access:
    username-foo:
      - path: /
        permissions: rwX
        recursive: true
    username-bar:
      - path: /
        permissions: rX
      # ... more entries ...

The most elegant way to handle this in Ansible is using the with_subelements loop construct. Here's how to implement it:

- name: Set filesystem permissions
  shell: |
    setfacl -R -m u:{{ item.0.key }}:{{ item.1.permissions }} {{ item.1.path }}
    {% if item.1.recursive is defined and item.1.recursive %}
    setfacl -R -m d:u:{{ item.0.key }}:{{ item.1.permissions }} {{ item.1.path }}
    {% endif %}
  with_subelements:
    - "{{ access | dict2items }}"
    - value
  loop_control:
    label: "{{ item.0.key }}:{{ item.1.path }}"

For more control over the iteration, we can use dict2items filter combined with nested loops:

- name: Process permissions
  include_tasks: process_permission.yml
  loop: "{{ access | dict2items }}"
  loop_control:
    loop_var: user_entry

# In process_permission.yml
- name: Set permissions for each path
  shell: |
    setfacl -m u:{{ user_entry.key }}:{{ path_item.permissions }} {{ path_item.path }}
  loop: "{{ user_entry.value }}"
  loop_control:
    loop_var: path_item

For more complex scenarios where you need additional processing:

- name: Advanced permission handling
  block:
    - name: Debug user permissions
      debug:
        msg: "User {{ user }} will get {{ perm.permissions }} on {{ perm.path }}"
      loop: "{{ access[user] }}"
      loop_control:
        loop_var: perm
      vars:
        user: "{{ item }}"
      with_items: "{{ access.keys() | list }}"

If the nested structure becomes too complex, consider flattening it:

flattened_access:
  - user: username-foo
    path: /
    permissions: rwX
    recursive: true
  - user: username-bar
    path: /
    permissions: rX
  # ... more entries ...

- name: Process flattened structure
  shell: setfacl -m u:{{ item.user }}:{{ item.permissions }} {{ item.path }}
  loop: "{{ flattened_access }}"

When dealing with complex permission structures in Ansible, we often encounter nested dictionaries containing lists. The example shows a common scenario for managing filesystem access:

access:
    username-foo:
      - path: /
        permissions: rwX
        recursive: true
    username-bar:
      - path: /
        permissions: rX
      - path: /css
        permissions: rwX
        recursive: true

The optimal way to handle this structure is using Ansible's with_subelements lookup combined with dict2items filter:

- name: Set filesystem permissions
  ansible.builtin.shell: |
    setfacl -R -m u:{{ item.0.key }}:{{ item.1.permissions }} {{ item.1.path }}
  loop: "{{ access | dict2items | subelements('value') }}"
  when: item.1.path is defined

For paths requiring recursive permission changes, we can add conditional logic:

- name: Set recursive permissions
  ansible.builtin.shell: |
    {% if item.1.recursive | default(false) %}
    setfacl -R -m u:{{ item.0.key }}:{{ item.1.permissions }} {{ item.1.path }}
    {% else %}
    setfacl -m u:{{ item.0.key }}:{{ item.1.permissions }} {{ item.1.path }}
    {% endif %}
  loop: "{{ access | dict2items | subelements('value') }}"

For more complex scenarios, consider using Jinja2 templates to generate the commands:

- name: Generate permission commands
  ansible.builtin.template:
    src: permissions.j2
    dest: /tmp/set_permissions.sh
    mode: '0755'

# permissions.j2 content:
{% for user,paths in access.items() %}
{% for entry in paths %}
{% if entry.recursive | default(false) %}
setfacl -R -m u:{{ user }}:{{ entry.permissions }} {{ entry.path }}
{% else %}
setfacl -m u:{{ user }}:{{ entry.permissions }} {{ entry.path }}
{% endif %}
{% endfor %}
{% endfor %}

Always include validation in your playbook to catch potential issues:

- name: Validate permission entries
  ansible.builtin.assert:
    that:
      - "'path' in item.1"
      - "'permissions' in item.1"
    fail_msg: "Missing required fields in permission entry"
  loop: "{{ access | dict2items | subelements('value') }}"