Require a minimum Ansible version in your Playbook

It's helpful to be able to enforce a minimum required Ansible version in Ansible playbooks. Ansible Roles have long been able to specify a minimum Ansible version—but only for Ansible Galaxy and ansible-galaxy-related dependency management.

I've found more and more that users who installed Ansible further in the past (in the 1.7.x or 1.8.x era) are now using some of my newer projects that require Ansible 2.0 (there are so many nice new shiny things!), and they're running into errors like:

ERROR: [DEPRECATED]: include + with_items is a removed deprecated feature.  Please update your playbooks.
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.

The problem, as it turns out, is that these users are running a version < 2.0, but it's not very obvious based on that error message!

Luckily, Ansible has the helpful assert module, and Ansible also provides a global ansible_version dict with the full version string, major and minor versions, etc.—see this output for the var from the debug module in a simple test playbook:

"ansible_version": {
    "full": "2.1.1.0",
    "major": 2,
    "minor": 1,
    "revision": 1,
    "string": "2.1.1.0"
}

Using these two tools, we are able to build a task in our playbook that will check that the version of Ansible being used to run the playbook meets a minimum (or even exact) requirement.

In Drupal VM's case, I added the task below as the first task in my playbook's pre_tasks:

---
- hosts: all
  pre_tasks:
    - name: Verify Ansible meets Drupal VM's version requirements.
      assert:
        that: "ansible_version.full is version_compare('2.1', '>=')"
        msg: >
          "You must update Ansible to at least 2.1 to use this version of Drupal VM."

Now, if a user runs Drupal VM's playbook with an outdated version of Ansible, they'll get the following warning:

TASK [Verify Ansible meets Drupal VM's version requirements.] ******************
fatal: [drupalvm]: FAILED! => {"assertion": "ansible_version.full is version_compare('2.1', '>=')", "changed": false, "evaluated_to": false, "failed": true, "msg": ""You must update Ansible to at least 2.1 to use this version of Drupal VM."\n"}

This is a much more helpful debug message than other 'xyz module failed' messages. Unfortunately, there are still cases (like the one mentioned in this post originally) where this task won't help, because the playbook itself won't be run (since the older version of Ansible can't even parse the full YAML structure correctly).

So for those cases, it would be nice if Ansible provided a built-in mechanism (maybe in a project ansible.cfg file) to specify a minimum required Ansible version, like what Vagrant does. You can technically write a callback plugin to do the version check, but this seems like overkill to me. See these three issues for further information:

Comments

Kind of bothersome that meta/main.yml has a minimum version attribute `min_ansible_version` you'd think there'd be some logic already capable of, at the very least, warning the user that they are using an unsupported older version of ansible...

re: 'unsupported older version' of ansible, unfortunately we don't all control which versions of ansible our upstream providers make available to us - example would be centos which has very long-lived versions and very old python.

centos6 has 1.9.2, but if you enable the epel repo you can get to 2.x with warnings/errors that lead you into pip-vs-rpm version hell ala:

/usr/lib64/python2.6/site-packages/cryptography/__init__.py:26: DeprecationWarning: Python 2.6 is no longer supported by the Python core team, please upgrade your Python. A future version of cryptography will drop support for Python 2.6
DeprecationWarning
ansible 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides

Really nice to find this post.
We have a set of playbooks that span a number of use cases, some of them are in need of updates while others demand the latest.
Having a check for each play that tells you if that play will fail due to version differences is really helpful. It also helps to remind your team that an update is available if you don't have a centralized updating method across a team that uses many different OS's.

Thanks for a helpful piece of code.
Surely Ansible runs only on localhost? Shouldn't this run with
hosts: localhost
rather than
hosts: all

hosts: all just means "run it on any host defined in inventory at the current time. In Drupal VM's case, that means the host that is running the VM, in VirtualBox most likely. For Drupal VM, Ansible could be installed on the host or in the guest, using Vagrant's ansible provisioning mechanism.

But you're right, if you want to check the Ansible version on the actual host machine, you might want to add a delegate_to: localhost on that task.

Using task in Ansible 2.7, I get the following warning:

[DEPRECATION WARNING]: Using tests as filters is deprecated. Instead of using `result|version_compare` use `result is version_compare`. This feature will be removed in version 2.9. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

I don't really know what I'm doing, but it seems like just replacing the pipe with `is` silences the warning. Does this do the same thing the original does? Are there any issues with it?

Yep! Thanks for pointing this out, I've updated the example in the blog post, switching the | to is.

Hi,
I've implemented your tutorial (thank you very much for that), but now I'm wondering if there's a way to find the compatibility level of any given playbook by running a compatibility checker. At the moment I'd have to manually go through every single one of my playbooks, see when a given module was introduced and make a long list to find the minimal required Ansible version. I would then have to install that Ansible version and test my playbook to see if there's any unsupported/deprecated language feature . As you see, this sounds rather laborious just to find the number to put into ## assert: that: "ansible_version.full is version_compare('VERSION_HERE', '>=')" ##
The alternative would be to just put in the Ansible version that a) was recent when the playbook was created or b) when is recent at the last edit date, but those both don't sound too satisfying to me...