Search code examples
jsonansiblejmespath

Filtering elements in nested list with json_query


I'd like to filter out entries from a nested list depending on a presence of a key. Given the following dictionary:

hostvars:
  host1:
    backups:
      - src: somesource
        target: sometarget
      - src: anothersource
  host2:
    backups:
      - src: somesource for host2
        target: fancy target
  host3:
    backups:
      - src: yet another src
      - src: and another one

I'd like to filter out all elements of the backups lists when the target key is not present.

The closest I've come is:

- set_fact:
    data: "{{ hostvars | dict2items | json_query(query) }}"
  vars:
    query: "[?value.backups[?target]]"

which results in

hostvars:
  host1:
    backups:
      - src: somesource
        target: sometarget
      - src: anothersource
  host2:
    backups:
      - src: somesource for host2
        target: fancy target

So I've successfully filtered out host3 which does not have an element containing the target key in the backups list.
However, I'd also like to remove the second element from the backups list of host1 (which also does not contain the target key).

Any pointers are greatly appreciated.


Solution

  • For example

        - set_fact:
            data: "{{ hostvars|
                      dict2items|
                      json_query(_query)|
                      selectattr('value')|
                      items2dict }}"
          vars:
            _query: '[].{key: key, value: value.backups[?target]}'
          run_once: true
    

    gives

      data:
        host1:
        - src: somesource
          target: sometarget
        host2:
        - src: somesource for host2
          target: fancy target
    

    The next option is adding the default attribute target: None if missing, e.g.

        - set_fact:
            backups: "{{ [{'target': None}]|product(backups)|map('combine') }}"
        - debug:
            var: backups
    

    gives

    TASK [debug] ****************************************************
    ok: [host1] => 
      backups:
      - src: somesource
        target: sometarget
      - src: anothersource
        target: null
    ok: [host2] => 
      backups:
      - src: somesource for host2
        target: fancy target
    ok: [host3] => 
      backups:
      - src: yet another src
        target: null
      - src: and another one
        target: null
    

    Then, select 'not null' targets

        - set_fact:
            data: "{{ hostvars|
                      json_query('*.backups')|
                      map('selectattr', 'target')|
                      flatten }}"
          run_once: true
        - debug:
            var: data
          run_once: true
    

    gives

    TASK [debug] ****************************************************
    ok: [host1] => 
      data:
      - src: somesource
        target: sometarget
      - src: somesource for host2
        target: fancy target
    

    To identify the host add this attribute to the lists too, .e.g

        - set_fact:
            backups: "{{ [{'target': None, 'host': inventory_hostname}]|
                         product(backups)|map('combine') }}"
    

    gives the result

    TASK [debug] ****************************************************
    ok: [host1] => 
      data:
      - host: host1
        src: somesource
        target: sometarget
      - host: host2
        src: somesource for host2
        target: fancy target