Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to version sort by an attribute of list members

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
like image 521
zagpoint Avatar asked Dec 06 '25 15:12

zagpoint


1 Answers

There are more options:

  1. Create an index. The declaration below removes 'host' from the string. Fit it to your needs
  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

  1. Create a dictionary from the index
  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

  1. Create a custom filter. For example,
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
like image 120
Vladimir Botka Avatar answered Dec 08 '25 22:12

Vladimir Botka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!