How to compare RPM versions

Is there a test, similar to ansible.builtin.version, which knows how to compare RPM version strings, using the same algorithm that the rpm and yum commands use?

Or even better, the problem I’m hoping to solve with this … is there a way to make ansible.builtin.package support the idea of “version X or later”, so we can enforce required minimums in response to security notices?

I am not aware of one, though it would be fairly straightforward to write a custom test for this.

ansible.builtin.package is just a convenience wrapper that calls whatever package module is appropriate for your system, it does not itself care what parameters you pass. To find out what syntax is actually supported by the module being called, you have to consult its documentation.

Most package installation modules permit you to place constraints on the version installed; since you asked about RPMs, you’re probably using either the yum or dnf module, which use identical syntax for this:

- ansible.builtin.package:
      # Install an exact release
      - openssh-8.7p1-29.el9_2
      # Install a less exact version
      - openssl-3.0.7
      # Constrain the minimum version
      - git >= 2.40.1
    state: present

Thank you for that - I remember reading that ansible.builtin.package was just a wrapper, but it didn’t “click” at the time. Too many things going on at once, and not enough coffee in the world, I guess.

In my case, I need it to work with ansible.builtin.yum (for CentOS/RHEL 7), ansible.builtin.dnf (for RHEL 9), and ansible.builtin.apt (for Debian 12).

What ended up working was something very similar to this (lists of package names and a few other company-specific things removed, but this should be enough for others to see what’s happening) …

# ROLE/vars/main.yml
pkg_min :
  CentOS7 :
    ca-certificates : '2023.2.60_v7.0.306-72.el7_9'
  RedHat7 :
    ca-certificates : '2023.2.60_v7.0.306-72.el7_9'
  RedHat9 :
    ca-certificates : '2023.2.60_v7.0.306-90.1.el9_2'
  Debian12 :
    ca-certificates : '20230311'
# ROLE/tasks/main.yml
- name : "Install/update packages with minimum versions"
  vars :
    os    : "{{ ansible_facts.distribution }}"
    maj   : "{{ ansible_facts.distribution_major_version }}"
    osmaj : "{{ os ~ maj }}"
    pkg   : "{{ pkg_min[osmaj] }}"
    dfv   : "{{ ansible_facts.distribution_file_variety }}"
    gt    : "{{ ( dfv == 'Debian' ) | ternary( '>=' , ' >= ' ) }}"
  loop : "{{ pkg | d( {} , true } | dict2items }}"
  ansible.builtin.package :
    name  : "{{ item.key ~ ge ~ item.value }}"
    state : present

The final detail was the fact that yum and dnf require spaces between the package name, operator, and version, while apt requires that there NOT be spaces between them. I handled this with the gt variable, which either does or does not include the spaces.

One thing I’ve noticed is that doing it this way makes the target machine run a yum or apt command for every package name on the list, which can be rather slow.

I had already figured out a more convoluted way, using ansible.builtin.package_facts to figure out what’s already on each machine, breaking apart the (epoch,version,release), and doing the (hopefully) correct comparisons in a when: clause, so the only time a yum/apt command is run on a target machine is if the package needs to be installed or updated. The Ansible “code” for this isn’t pretty to look at, but it seems to work and it runs a LOT more quickly than what I showed above.

Which leads me back to my original question … is there a way to compare version numbers, as a test? Or do I still need to bite the bullet, learn python, and update ansible.builtin.version to support version_type : rpm?

It is recommended to provide a list of packages to packaging modules so the underlying package managers can process them in a batch (and satisfy the input in the most efficient way) rather than looping over each package and processing them individually, as you have identified.

To your example, IMHO it seems like you are duplicating the work that the package managers should do. Is there a reason not to use the example @flowerysong provided but extend it to be distribution specific? Roughly:

- hosts: my_hosts
        - "ca-certificates >= 2023.2.60_v7.0.306-72.el7_9"
        - "ca-certificates >= 2023.2.60_v7.0.306-72.el7_9"
        - "ca-certificates >= 2023.2.60_v7.0.306-90.1.el9_2"
        - "ca-certificates>=20230311"
    - package:
        name: "{{ pkgs[ansible_distribution~ansible_distribution_major_version] }}"

The advantage is that there is almost no templating logic needed and it is more generic as you can provide, say, just package names without version constraints like vim in the list.

Because the documentation doesn’t mention that you CAN use a list of names like that. All it says for the name parameter’s data type is “string / required”.

There seems to be a LOT of oversights like this in the Ansible documentation. I’ll be honest, Ansible was not my choice (especially after working with Puppet for 11 years), but I’m dealing with it as best I can. Poor documentation like this is one of the reasons that, after four months, I’m still not a fan of Ansible. I can work with it, I just don’t like it.

And I STILL think that ansible.builtin.version needs to support comparing RPM version strings.

The package documentation covers the common features of different backends, but the underlying package manager modules often support and document additional features (like allowing a list of packages).

This is the option to select the backend module ansible.builtin.package module – Generic OS package manager — Ansible Documentation. By default it’s auto-detected, but you could look up the documentation for the module it selects by looking at the playbook output.

For example, apt module accepts a list of packages: ansible.builtin.yum module – Manages packages with the yum package manager — Ansible Documentation
yum accepts a list of packages: ansible.builtin.yum module – Manages packages with the yum package manager — Ansible Documentation

I found that … and luckily, dnf (for RHEL 9) also supports name as an array, so for my purposes I’m good - and maybe later today I’ll have time to try using a list of “package” or “package >= N” or “package>=N” strings to see how that goes.

Assuming it works (and is faster), my one concern is that the output won’t have a list of which packages it installed or upgraded, which is likely to make the team who manages the production servers, a bit nervous. They like being able to see what (Puppet, Ansible, whatever) is doing (and I would too, if I were on that team).

I have never used this myself, but check out this thread here on the forum, it talks about modifying what the ansible output shows ==> Ansible Output Label <== maybe that can help display what you want.

Or, if that doesn’t work or is too complicated, you can always add some steps to read the /var/log/yum.log (or the corresponding log if not yum), and then echo to the screen/ansible log what was installed today.