ReDNaX
ReDNaX

Reputation: 101

How to sort version numbers in Ansible

I'm building an Ansible playbook in which I want to make a backup of a database in case I need to upgrade the software. For this I want to compare the highest version number that is available to the version number that is installed. In case the latest version is hight than the installed version I'll back up the database.

The problem however is that I cannot find a good way to sort version numbers in Ansible. The standard sort filter sorts on strings instead of numbers/versions.

This is what I'm doing right now:

- name: Get package version
  yum:
    list: package
  register: software_version

- name: Read which version is installed and available
  set_fact:
    software_version_installed: "{{ software_version | json_query(\"results[?yumstate=='installed'].version\") | sort | last }}"
    software_version_available: "{{ software_version | json_query(\"results[?yumstate=='available'].version\") | sort | last }}"

- name: Backup old database file on remote host
  copy:
    src: "{{ software.database_path }}"
    dest: "{{ software.database_path }}_{{ ansible_date_time.date }}_v{{ software_version_installed }}"
    remote_src: yes
  when: software_version_installed is version(software_version_available, "<")

The playbook above works, as long as version numbers stay underneath the number 10 (e.g. 1.2.3, but not 1.10.1) since sorting is performed like a string. When the version number has to sort e.g. 1.2.3 and 1.10.1, it will take 1.2.3 as latest version.

To show the issue:

- name: Read which version is installed and available
  set_fact:
    software_versions: [ "2.5.0", "2.9.0", "2.10.0", "2.11.0" ]

- name: Debug
  debug:
    var: software_versions | sort

TASK [grafana : Debug]**********************************
ok: [localhost] => {
    "software_versions | sort": [
        "2.10.0",
        "2.11.0",
        "2.5.0",
        "2.9.0"
    ]
}

Does anyone know a good way to sort version numbers in Ansible?

Upvotes: 7

Views: 6091

Answers (4)

bendem
bendem

Reputation: 365

All the answers here provide a custom way of sorting versions, I'd like to point out that as of v2.10, Ansible can do this via the version_sort filter already:

- name: Sort list by version number
  debug:
    var: ansible_versions | community.general.version_sort
  vars:
    ansible_versions:
      - '2.8.0'
      - '2.11.0'
      - '2.7.0'
      - '2.10.0'
      - '2.9.0'

See also Filter Guide - Working with versions.

Upvotes: 7

Will H
Will H

Reputation: 21

The current accepted answer offers a very nice solution, using a filter_plugin. Unfortunately, the distutils Python package that is uses appears to be deprecated. Some googling led me to the packaging package, which offers a similar Version class. Here's an updated filter_plugin that doesn't use distutils:

from packaging.version import Version

def version_sort(l):
    return sorted(l, key=Version)

class FilterModule(object):

    def filters(self):
        return {
            'version_sort': version_sort
        }

It's working well for us, but I don't want to promise that the behavior will be exactly the same in every situation.

Upvotes: 2

Vladimir Botka
Vladimir Botka

Reputation: 68044

Q: Does anyone know a good way to sort version numbers in Ansible?

A: Use filter_plugin. For example the filter

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

def version_sort(l):
    return sorted(l, key=LooseVersion)

class FilterModule(object):

    def filters(self):
        return {
            'version_sort' : version_sort
            }

with the playbook

shell> cat test-versions.yml
- name: Sort versions
  hosts: localhost

  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|version_sort }}"

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"
    ]

For your convenience, the filter is available at Github ansible-plugins.


Version comparison does the job to iterate the list and compare items. See the example below

shell> cat test-versions.yml
- hosts: localhost
    vars:
      version_installed: "1.10.1"
      versions:
        - "1.1.3"
        - "1.2.3"
        - "1.7.5"
        - "1.10.7"
    tasks:
      - debug: msg="{{ item }} is newer than {{ version_installed }}"
        loop: "{{ versions }}"
        when: item is version(version_installed, '>')
shell> ansible-playbook test-versions.yml | grep msg
"msg": "1.10.7 is newer than 1.10.1"

Upvotes: 5

ReDNaX
ReDNaX

Reputation: 101

It's now solved in another way. Instead of sorting the versions I compared the current version to all available versions.

  • I've started by setting an update variable to false
  • Next I compared the installed version to every available version
  • If installed version > current version, set the update variable to true

The task performing the backup will only be performed when the update variable is true.

- name: Get package version
  yum:
    list: package
  register: software_version

- name: Read which version is installed and available
  set_fact:
    software_update: false
    software_version_installed: "{{ software_version | json_query(\"results[?yumstate=='installed'].version\") | last }}"
    software_version_available: "{{ software_version | json_query(\"results[?yumstate=='available'].version\") }}"

- name: Check if upgrade is needed
  set_fact:
    software_update: true
  when: software_version_installed is version(item, "<")
  with_items: "{{ software_version_available }}"

- name: Backup old database file on remote host
  copy:
    src: "{{ software.database_path }}"
    dest: "{{ software.database_path }}_{{ ansible_date_time.date }}_v{{ software_version_installed }}"
    remote_src: yes
  when: software_update

Upvotes: 3

Related Questions