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?
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