Search code examples
ansiblejinja2nested-loopsansible-2.xansible-filter

Reducing complex vars (dicts of lists) in Ansible to simple lists


I have searched, scanned, and tried multiple solutions and found none yet that fit. This seems like a simple operation. Given Ansible vars of (for example) usernames and privileges, I want to derive filenames (for templates) to apply to each combination.
Oversimplified example:

- task: user printing access
  vars:
    useraccess:
      superdude:
        - lpr
        - lpc
        - lpadmin
      nonprivuser:
        - lpr
  loop: # I'm at a loss
  debug:
    msg: "{{ some jinja2 expression using item transform }}""

The result I want from loop and whatever transforms/filters should be as though I had specified:

- task: user printing access
  vars:
    useraccess:
      - superdude/lpr
      - superdude/lpc
      - superdude/lpadmin
      - nonprivuser/lpr
  loop: "{{ useraccess }}"
  debug:
    msg: "{{ item }}"

I'v experimented with map, product, dictitem, and a few others with no success at yet.


Solution

  • Once you use dict2items on a dictionary, you have a key and a value attributes. Since your value is a list, you can now use the subelements filter over it.

    Given the task:

    - set_fact:
        folders: "{{ folders | default([]) + [item.0.key ~ '/' ~ item.1] }}"
      loop: "{{ useraccess | dict2items | subelements('value') }}"
      loop_control:
        label: "{{ item.0.key }}/{{ item.1 }}"
      vars:
        useraccess:
          superdude:
            - lpr
            - lpc
            - lpadmin
          nonprivuser:
            - lpr
    

    This would give you the list:

    folders:
      - superdude/lpr
      - superdude/lpc
      - superdude/lpadmin
      - nonprivuser/lpr
    

    And if you need a list right away, you can build upon it, as, after using the subelements filters, you can now map, from one side the users (the key of the dictionary — .0.key) and from the other, the sub-folders (each elements of given by the subelements filter — .1), zip those two lists together, and finally map a join in order to flatten the list of lists:

    So, given the task:

    - debug:
        var: |-
          _subelements_list | map(attribute='0.key')
            | zip(_subelements_list | map(attribute='1'))
            | map('join', '/')
      vars:
        _subelements_list: "{{ useraccess | dict2items | subelements('value') }}"
        useraccess:
          superdude:
            - lpr
            - lpc
            - lpadmin
          nonprivuser:
            - lpr
    

    Ansible would yield:

    ok: [localhost] => 
      ? |-
        _subelements_list | map(attribute='0.key')
          | zip(_subelements_list | map(attribute='1'))
          | map('join', '/')
      : - superdude/lpr
        - superdude/lpc
        - superdude/lpadmin
        - nonprivuser/lpr