Create list of dict filtering different value with Ansible

Viewed 199

I'm looking for a better solution to filter a list of dicts by a key A and return a list of value of key B. To be more concrete - every host has a dict:

infrastructure:
  name: "x..."
network:
  address: "1..."

There are hosts, where network.address is defined and there are hosts, where network.address is not defined. I need now a list of all infrastructure.name with defined network.address.

- name: "Define Alias fact"
  set_fact:
    alias: []

- name: "Add Aliases for all hosts with network.address is defined"
  set_fact:
    alias: "{{ alias + [hostvars[host].infrastructure.name + '-alias'] }}"
  when:
    - "hostvars[host].network is defined"
    - "hostvars[host].network.address is defined"
  with_items: "{{ groups['all'] }}"
  loop_control:
    loop_var: host

That works, but is a little bit messy, because I call set_fact many times and add items to a list.

When I have a look at:

- name: "Define addresses fact"
  set_fact:
    address: "{{ groups['all'] | map('extract', hostvars) | list | json_query('[*].network.address') }}"

This is much shorter, maybe easier.

I'd like to ask, if I can use map and extract and the "list of dicts" before flatten the list to "filter out" all items where network.address is not defined and use json_query together with some string operation to append the '-alias'. Is there a similar easy way to replace the first script?

1 Answers

In a pure JMESPath way, given the JSON

[
  {
    "infrastructure": {"name": "x..."},
    "network": {"address": "1..."}
  },
  {
    "infrastructure": {"name": "y..."}
  },
  {
    "infrastructure": {"name": "z..."},
    "network": {"address": "2..."}
  },
  {
    "infrastructure": {"name": "a..."},
    "network": {}
  }
]

You can extract the infrastructure.name concatenated with -alias having a network.address set this way:

[?network.address].join('-', [infrastructure.name, 'alias'])

This will yield:

[
  "x...-alias",
  "z...-alias"
]

The function join is primarily meant to glue array elements together, but it can also be used to concatenate string.


And so for a playbook demonstrating this:

- hosts: all
  gather_facts: no
      
  tasks:
    - debug: 
        msg: >-
          {{ 
            servers | to_json | from_json | 
            json_query(
              '[?network.address].join(`-`, [infrastructure.name, `alias`])'
            ) 
          }}
      vars:
        servers:
          - infrastructure:
              name: x...
            network:
              address: 1...
          - infrastructure:
              name: y...
          - infrastructure:
              name: z...
            network:
              address: 2...
          - infrastructure:
              name: a...
            network:

Note that the odd construct | from_json | to_json is explained in this other answer

This yields:

PLAY [all] ********************************************************************************************************

TASK [debug] ******************************************************************************************************
ok: [localhost] => {
    "msg": [
        "x...-alias",
        "z...-alias"
    ]
}

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