Version numbers in Ansible code

There are two useful Jinja filters that could help dealing with version numbers in Ansible code. One is version_compare, which allows to compare version numbers. The other is regex_replace, which can be used to get a major.minor version number from a longer version number.

Version comparison

It is possible to compare a simple version to an integer or decimal, to decide, whether to, for example, run a task in Ansible. That, however, becomes a bit harder, when using version numbers, that are more complex and are not really numbers, e.g. 3.2.1. One can use the split function - split the version by dots and compare the results, but it's not that clean:

when: "version.stdout.split('.')[0] >= '2' and version.stdout.split('.')[1] > '7'"

Fortunately, there is a version_compare jinja filter, that can be used in Ansible to compare even more complex versions:

- hosts: "localhost"
  gather_facts: false
  vars:
    version: "2.7.11"
  tasks:
    - name: check if version is within acceptable range
      assert:
        that:
          - version | version_compare('2.7', '>=', strict=True)
          - version | version_compare('4.0', '<', strict=True)

The second parameter of version_compare (relational operator) can be noted as above, or using letter references (le, gt...) as well.

The documentation on the strict parameter is very brief, but the testing seems to show that it indeed uses stricter version notation rules on both sides of comparison: it requires at least one dot in the version number, and does not allow more than 2 dots. Setting version variable in the code above to 3 or 3.0.0.0 would not work, while 3.0 or 3.0.1 would be fine with the strict parameter present.

Version shortening

We'll use Python as an example, which has a version numbering scheme, that always has 2 dots, e.g. 2.7.11 or 3.5.1. In some cases, we must use Python executables, that are suffixed by a shortened version number, e.g. python2.7. This means that we somehow need to reliably turn 2.7.11 into 2.7.
Another jinja filter, regex_replace, seemed the most efficient and reliable way to do just that. This regexp seemed to do the trick:

version | regex_replace('^([0-9])\\.([0-9]*).*', '\\1.\\2')

There is a caveat when using Ansible < 2.x:

Prior to ansible 2.0, if “regex_replace” filter was used with variables inside YAML arguments (as opposed to simpler ‘key=value’ arguments), then you needed to escape backreferences (e.g. \1) with 4 backslashes (\\\\) instead of 2 (\\).

So, for Ansible versions < 2.x, the code would look like this:

version_var | regex_replace('^([0-9])\\.([0-9]*).*', '\\\\1.\\\\2')