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

Viewed 35

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
2 Answers

The following is bit of a pain to debug in case of problem but it gives the expected result. The playbook:

---
- hosts: localhost
  gather_facts: false

  vars:
    docker_applications:
      - name: foo
        zfs_opts:
          quota: 4G
          compression: false
      - name: bar
    _default_zfs_filesystems:
      - name: rust/ftp
      - name: ssd/one
        quota: 1T
      - name: ssd/two

    _docker_zfs_filesystems: >-
      {%- set dzfs = [] -%}
      {%- for i in docker_applications -%}
        {%- set cur = {'name': 'docker/' ~ i.name} -%}
        {%- for k, v in (i.zfs_opts | d({})).items() -%}
        {{ cur.update({k: v }) }}
        {%- endfor -%}
        {{ dzfs.append(cur) }}
      {%- endfor -%}
      {{ dzfs }}

    zfs_filesystems: "{{ _default_zfs_filesystems + _docker_zfs_filesystems }}"

  tasks:
    - debug:
        var: zfs_filesystems

gives:

PLAY [localhost] ***********************************************************************************************************************************************

TASK [debug] ***************************************************************************************************************************************************
ok: [localhost] => {
    "zfs_filesystems": [
        {
            "name": "rust/ftp"
        },
        {
            "name": "ssd/one",
            "quota": "1T"
        },
        {
            "name": "ssd/two"
        },
        {
            "compression": false,
            "name": "docker/foo",
            "quota": "4G"
        },
        {
            "name": "docker/bar"
        }
    ]
}

PLAY RECAP *****************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

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
Related