dio
dio

Reputation: 63

How to sort complex version numbers in Ansible

I'm building an Ansible playbook in which I want to retrieve the latest version of a software. For this I used "sort" filter in Ansible. That, however, becomes a bit harder, when using version numbers, that are more complex and are not really numbers, e.g. 0.2.1, 0.10.1.

This is what I'm doing right now:

- name: Set version to compare
  set_fact:
    versions:
      - "0.1.0"
      - "0.1.5"
      - "0.11.11"
      - "0.9.11"
      - "0.9.3"
      - "0.10.2"
      - "0.6.1"
      - "0.6.0"
      - "0.11.0"
      - "0.6.5"

- name: Sorted list
  debug:
    msg: "{{ versions | sort }}"

- name: Show the latest element
  debug:
    msg: "{{ versions | sort | last }}"

The playbook above works, as long as version numbers stay underneath the number 10 (e.g. 0.9.3, but not 0.10.2).

To show the issue:

TASK [Set version to compare] ***************************************************************************************************************
ok: [localhost]

TASK [Sorted list] **************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "0.1.0",
        "0.1.5",
        "0.10.2",
        "0.11.0",
        "0.11.11",
        "0.6.0",
        "0.6.1",
        "0.6.5",
        "0.9.11",
        "0.9.3"
    ]
}

TASK [Show the latest element] **************************************************************************************************************
ok: [localhost] => {
    "msg": "0.9.3"
}

In this example the value the desired value is 0.11.11

Does anyone know a good way to sort complex version numbers in Ansible? Any help would be appreciated. Thanks.

Upvotes: 6

Views: 1741

Answers (4)

Vladimir Botka
Vladimir Botka

Reputation: 68044

An option would be to write a filter plugin. For example,

shell> cat filter_plugins/sort_versions.py
from distutils.version import LooseVersion


def sort_versions(value):
    return sorted(value, key=LooseVersion)


class FilterModule(object):

    def filters(self):
        return {
            'sort_versions': sort_versions,
        }

Then the task below

    - debug:
        msg: "{{ versions|sort_versions }}"

gives

  msg:
  - 0.1.0
  - 0.1.5
  - 0.6.0
  - 0.6.1
  - 0.6.5
  - 0.9.3
  - 0.9.11
  - 0.10.2
  - 0.11.0
  - 0.11.11

You don't have to write the filter if you can install the collection community.general. Use the filter community.general.version_sort. Then, the task below gives the same result

    - debug:
        msg: "{{ versions|community.general.version_sort }}"

Example of a complete playbook for testing

- hosts: all

  vars:

    versions:
      - '0.1.0'
      - '0.1.5'
      - '0.11.11'
      - '0.9.11'
      - '0.9.3'
      - '0.10.2'
      - '0.6.1'
      - '0.6.0'
      - '0.11.0'
      - '0.6.5'

  tasks:

    - debug:
        msg: "{{ versions|sort_versions }}"

    - debug:
        msg: "{{ versions|community.general.version_sort }}"

Upvotes: 5

Liran Ben Abu
Liran Ben Abu

Reputation: 31

You can use Jinja2 compare version instead of installing a filter plugin

- name: test
  set_fact:
    max_number: "{{ item }}"
  when: max_number |default('0') is version(item, '<')
  loop: "{{ master_version }}"

just follow Playbook Test Comparing versions.

Upvotes: 3

Joseph Attard
Joseph Attard

Reputation: 51

I had a similar use case where I had a version and needed to obtain the previous version from a list of versions.

---

- name: previous_version_filter
  hosts: localhost
  gather_facts: false

  vars:
    versions:
      - "21.7.1"
      - "21.13.0"
      - "21.7.2"
      - "21.13.1"
      - "21.8.0"
      - "21.7.0"

    version: "21.13.0"
    newer_versions: []

  tasks:
    - name: Create newer_versions list
      set_fact:
        newer_versions: "{{ newer_versions + [item] }}"
      when: item is version(version, '>')
      loop: "{{ versions }}"

    - name: Create previous_versions list
      set_fact:
        previous_versions: "{{ versions | difference(newer_versions) | difference([ version ]) }}"

    - name: Obtain previous_version
      set_fact:
        previous_version: "{{ item }}"
      when: previous_version | default('0') is version(item, '<')
      loop: "{{ previous_versions }}"

    - debug:
        msg: "{{ previous_version }}"

Upvotes: 0

mdaniel
mdaniel

Reputation: 33203

Part of what you are looking for is the version test, however, it is built on the idea that a user just wants one comparison. So, you'll need to do some glue to find the "latest" one:

- set_fact:
    max_version: >-
      {%- set vmax = {} -%}
      {%- for v_1 in versions -%}
      {%- for v_2 in versions -%}
      {%- if v_1 is version(v_2, ">") and v_1 is version(vmax.get("max", "0.0.0"), ">") -%}
      {%- set _ = vmax.update({"max": v_1}) -%}
      {%- endif -%}
      {%- endfor -%}
      {%- endfor -%}
      {{ vmax.max }}

(I don't pretend that's the optimal solution, as it is very likely comparing versions against each other more than once, but it should work fine for small version lists)

Upvotes: 0

Related Questions