I am trying to get a list sorted properly by the "name" field in the below code sample:
---
- hosts: localhost
vars:
hosts:
- name: host2
uptime: 1d
- name: host10
uptime: 45d
- name: host1
uptime: 3m
tasks:
- name: version sort host list
debug:
#var: hosts | community.general.version_sort
#var: hosts | dictsort(false, 'value')
var: hosts | sort(attribute='name')
As you can see, it does not sort the hostnames properly (host2 should come before host10). I looked up the version_sort filter, but it does not support sorting by attribute. I understand that I wouldn't have been in this situation if the hostnames were properly padded. But it's what it is. I searched and did not see this asked. Any other ideas?
TASK [version sort host list] *************************************
ok: [localhost] => {
"hosts | sort(attribute='name')": [
{
"name": "host1",
"uptime": "3m"
},
{
"name": "host10", <-------
"uptime": "45d"
},
{
"name": "host2",
"uptime": "1d"
}
]
}
Summary:
Thanks to @Vladimir Botka for all the options! I consolidated choice #3 and came up with the playbook below. Note, I've updated the list of dicts to make it slightly more complex with the fqdn. But the solution works:
- hosts: localhost
vars:
hosts:
- {name: host2.example.com, uptime: 1d}
- {name: host10.example.com, uptime: 45d}
- {name: host1.example.com, uptime: 3m}
- {name: host3.example.com, uptime: 3m}
- {name: host15.example.com, uptime: 45d}
- {name: host20.example.com, uptime: 45d}
tasks:
# - debug:
# msg:
# - "index: {{ hosts | map(attribute='name') | community.general.version_sort }}"
# - "host_indexed: {{ dict(hosts|json_query('[].[name,@]')) }}"
# - "solution: {{ (hosts | map(attribute='name') | community.general.version_sort) | map('extract', dict(hosts|json_query('[].[name,@]'))) }}"
- debug:
var: (hosts | map(attribute='name') | community.general.version_sort) | map('extract', dict(hosts|json_query('[].[name,@]')))
Here is the result:
PLAY [localhost] *****************************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************************************************************************
[WARNING]: Collection community.general does not support Ansible version 2.14.17
ok: [localhost] => {
"(hosts | map(attribute='name') | community.general.version_sort) | map('extract', dict(hosts|json_query('[].[name,@]')))": [
{
"name": "host1.example.com",
"uptime": "3m"
},
{
"name": "host2.example.com",
"uptime": "1d"
},
{
"name": "host3.example.com",
"uptime": "3m"
},
{
"name": "host10.example.com",
"uptime": "45d"
},
{
"name": "host15.example.com",
"uptime": "45d"
},
{
"name": "host20.example.com",
"uptime": "45d"
}
]
}
PLAY RECAP ***********************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
There are more options:
index: "{{ hosts | map(attribute='name')
| map('regex_replace', 'host', '')
| map('int') }}"
gives
index: [2, 10, 1]
Note: For more sofisticated patterns use Python regular expressions. For example, the declarations below give the same result
regex: '^\D*(\d+)$'
replace: '\1'
index: "{{ hosts | map(attribute='name')
| map('regex_replace', regex, replace)
| map('int') }}"
Combine the index
hosts_indexed: "{{ index | map('community.general.dict_kv', 'index')
| zip(hosts)
| map('flatten')
| map('combine') }}"
gives
hosts_indexed:
- {index: 2, name: host2, uptime: 1d}
- {index: 10, name: host10, uptime: 45d}
- {index: 1, name: host1, uptime: 3m}
Now, sort the list
result: "{{ hosts_indexed | sort(attribute='index') }}"
gives
result:
- {index: 1, name: host1, uptime: 3m}
- {index: 2, name: host2, uptime: 1d}
- {index: 10, name: host10, uptime: 45d}
Example of a complete playbook for testing
- hosts: localhost
vars:
hosts:
- {name: host2, uptime: 1d}
- {name: host10, uptime: 45d}
- {name: host1, uptime: 3m}
index: "{{ hosts | map(attribute='name')
| map('regex_replace', 'host', '')
| map('int') }}"
hosts_indexed: "{{ index | map('community.general.dict_kv', 'index')
| zip(hosts)
| map('flatten')
| map('combine') }}"
result: "{{ hosts_indexed | sort(attribute='index') }}"
tasks:
- debug:
var: index | to_yaml
- debug:
var: hosts_indexed | to_yaml
- debug:
var: result | to_yaml
hosts_indexed: "{{ dict(index | zip(hosts)) }}"
gives
hosts_indexed:
1: {name: host1, uptime: 3m}
2: {name: host2, uptime: 1d}
10: {name: host10, uptime: 45d}
Now, sort the dictionary
result: "{{ hosts_indexed | dict2items
| sort(attribute='key')
| map(attribute='value') }}"
gives the same result, but without the attribute 'index'
result:
- {name: host1, uptime: 3m}
- {name: host2, uptime: 1d}
- {name: host10, uptime: 45d}
Example of a complete playbook for testing
- hosts: localhost
vars:
hosts:
- {name: host2, uptime: 1d}
- {name: host10, uptime: 45d}
- {name: host1, uptime: 3m}
index: "{{ hosts | map(attribute='name')
| map('regex_replace', 'host', '')
| map('int') }}"
hosts_indexed: "{{ dict(index | zip(hosts)) }}"
result: "{{ hosts_indexed | dict2items
| sort(attribute='key')
| map(attribute='value') }}"
tasks:
- debug:
var: index | to_yaml
- debug:
var: hosts_indexed | to_yaml
- debug:
var: result | to_yaml
shell> cat filter_plugins/my_sort.py
from distutils.version import LooseVersion
def my_sort(l):
return sorted(l, key=LooseVersion)
class FilterModule(object):
def filters(self):
return {
'my_sort': my_sort,
}
Use it to sort the names
index: "{{ hosts | map(attribute='name') | my_sort }}"
gives
index: [host1, host2, host10]
Use the attribute 'name' as a key
hosts_indexed: "{{ dict(hosts | json_query('[].[name, @]')) }}"
gives
hosts_indexed:
host1: {name: host1, uptime: 3m}
host10: {name: host10, uptime: 45d}
host2: {name: host2, uptime: 1d}
Now, use the list 'index' to extract the sorted list
result: "{{ index | map('extract', hosts_indexed) }}"
gives
result:
- {name: host1, uptime: 3m}
- {name: host2, uptime: 1d}
- {name: host10, uptime: 45d
Example of a complete playbook for testing
- hosts: localhost
vars:
hosts:
- {name: host2, uptime: 1d}
- {name: host10, uptime: 45d}
- {name: host1, uptime: 3m}
index: "{{ hosts | map(attribute='name') | my_sort }}"
hosts_indexed: "{{ dict(hosts | json_query('[].[name, @]')) }}"
result: "{{ index | map('extract', hosts_indexed) }}"
tasks:
- debug:
var: index | to_yaml
- debug:
var: hosts_indexed | to_yaml
- debug:
var: result | to_yaml