Search code examples
ansiblejinja2

Ansible: Looping over a list of dicts and merging with another dict of lists


I'm trying to work out how to loop over a list of dicts and extract the name of each entry to merge with another variable.

jinja2 pseudo code which should give the desired result:

---
docker_applications:
  - name: foo
    zfs_opts:
      quota: 4G
      compression: false
  - name: bar
zfs_filesystems:
  - name: rust/ftp
  - name: ssd/one
    quota: 1T
  - name: ssd/two
  {% for i in docker_applications %}
  - name: "docker/{{ i.name }}"
    {% for k, v in i.zfs_opts %}
    {{ k }}: {{ v }}
    {% endfor %}
  {% endfor %}

Which would give:

---
zfs_filesystems:
  - name: rust/ftp
  - name: ssd/one
    quota: 1T
  - name: ssd/two
  - name: docker/foo
    quota: 4G
    compression: false
  - name: docker/bar

I know you can't use jinja2 templates in a variable files, so I am trying to do the merge in the inventory file but I'm at a loss on how I could achieve this.

- name: nas
  hosts: nas
  become: true
  become_user: root
  roles:
    - docker
    - zfs
  vars:
    zfs_filesystems: zfs_filesystems|do_something

Solution

  • Put the declaration into the vars

    docker_fs: "{{ docker_applications|
                   json_query('[].[{name: join(`/`, [`docker`, name])}, zfs_opts]')|
                   map('combine')|list }}"
    

    gives

    docker_fs:
      - compression: false
        name: docker/foo
        quota: 4G
      - name: docker/bar
    

    Then, concatenate the lists

        - set_fact:
            zfs_filesystems: "{{ zfs_filesystems + docker_fs }}"
    

    gives

    zfs_filesystems:
      - name: rust/ftp
      - name: ssd/one
        quota: 1T
      - name: ssd/two
      - compression: false
        name: docker/foo
        quota: 4G
      - name: docker/bar
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        docker_applications:
          - name: foo
            zfs_opts:
              quota: 4G
              compression: false
          - name: bar
    
        zfs_filesystems:
          - name: rust/ftp
          - name: ssd/one
            quota: 1T
          - name: ssd/two
    
        docker_fs: "{{ docker_applications|
                       json_query('[].[{name: join(`/`, [`docker`, name])}, zfs_opts]')|
                       map('combine')|list }}"
    
      tasks:
    
        - debug:
            var: docker_fs
        - set_fact:
            zfs_filesystems: "{{ zfs_filesystems + docker_fs }}"
        - debug:
            var: zfs_filesystems
    

    The above solution puts the full value of the attribute zfs_opts into the dictionary. Optionally, select the attributes quota and compression. The declaration below

    docker_fs: "{{ docker_applications|
                   json_query('[].{name: join(`/`, [`docker`, name]),
                                   quota: zfs_opts.quota,
                                   compression: zfs_opts.compression}') }}"
    

    gives

    docker_fs:
      - compression: false
        name: docker/foo
        quota: 4G
      - compression: null
        name: docker/bar
        quota: null
    

    Optionally, get rid of the null values

    docker_fs: "{{ docker_applications|
                   json_query('[].{name: join(`/`, [`docker`, name]),
                                   quota: zfs_opts.quota,
                                   compression: zfs_opts.compression}')|
                                   map('dict2items')|
                                   map('rejectattr', 'value', 'eq', None)|
                                   map('items2dict')|
                                   list }}"
    

    gives

    docker_fs:
      - compression: false
        name: docker/foo
        quota: 4G
      - name: docker/bar