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


3 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') }}"