CfgMgmtCamp 2026 discussion (1/12): Supported Python versions, or: supporting older Operating Systems

As a user

Supported Python versions, or: supporting older Operating Systems

Latest developments

This was discussed at CfgMgmtCamp 2026.

Something is apparently happening here. There exists a PR for ansible-core that would allow the ansible.builtin.dnf module to also work with newer Python verions than the system Python. This is achieved by embedding a small script that can be run by the module with the system Python version. (This is similar to what I mentioned in a comment in May 2025.)

For now this is an experiment for the ansible.builtin.dnf module, but it might be extended to other package manager modules in the future. The good thing is that the PR adds a framework for making this easier.

Original text

I think this is one of the biggest pain points especially for long-time Ansible users. If you have to support older operating systems (I guess there are more discussions about this, these are the ones I remembered / was involved in), like RHEL 7, Rocky 8, or Debian 10, you find yourself quickly in the situation that some modules require Python libraries that are only available for the system Python. The system Python (might be Python 2.7, 3.6, …) unfortunately is no longer supported by a modern ansible-core version.

The standard recommendation is to use Execution Environments: Basically you take an old (sometimes ancient) version of ansible-core, put it into a container image, and use it to run your playbook. There are tools for making this easy (ansible-builder to build, ansible-navigator/AWX to run), but you cannot use features from newer ansible-core versions (like Data Tagging).

A consequence is that the collections you use also have to support this ansible-core version. Since collections from time to time stop supporting older ansible-core versions, this means that you cannot use modern collection versions either, so you’re missing bugfixes and new features from that side too.

(For example, community.general 12+ and community.crypto 3+ require ansible-core 2.17+, which requires Python 3.7+ on the target.)

Also making sure that your playbooks and roles work both with ancient ansible-core and collection versions and with new ones can be tricky. If you have to work with a larger (and diverse) zoo of targets, you might even find yourself in a position where you cannot use one execution environment that supports all of them. Sucks to be you, in that case, I guess?!

This makes Ansible pretty painful to use, and has made me think “should I really use Ansible for this?” more than once.

The reason for dropping support of old Python versions and ansible-core versions is that nobody wants to invest that much time in maintaining compatibility. If you don’t pay for that compatibility, why should you be able to expect it? From a developer’s point of view, this is very understandable. (I’m part of this - I also don’t want to spend keeping Python 2.7 or even 2.6 compatibility in collection code anymore.) For most modules this is also totally fine, since they talk to APIs or network devices and can (or even should) run on the controller.

So what can we do about it?

Usually it is possible to install newer Python versions also on old OSes. This allows you to use modern versions of ansible-core with most modules (that do not need to run with the system Python interpreter).

What about the situations in which you have to use a module that needs the system Python interpreter? Most common reason: interacting with the system’s package manager. Using ansible.builtin.command to install/remove/upgrade packages is not a good alternative.

Even worse: just for using ansbile.builtin.command you already need a newer Python version. The only ansible-core action running on the target that doesn’t need one is ansible.builtin.raw. If you have to restrict your playbook to only using ansible.builtin.raw, you might ask yourself why you’re using Ansible after all, and not something else…

It would be great to have a subset of core system modules that also work with old Python versions. I think that if we can fix this, we can already help a lot of users that have to deal with old OSes. Obviously this won’t be everyone, but it will be a lot better than the current state.

How can we get there?

Right now, there are multiple ways to write (non-Windows) modules (details can be found in lib/ansible/executor/module_common.py’s _find_module_utils):

  1. Use Python with ansible.module_utils.basic.AnsibleModule. When doing this, you are stuck to the target Pythons supported by ansible-core. You will get syntax errors with ansible-core 2.17+ when using Python 3.6 on the target, even if your module only uses Python 2.x syntax.

    (Instead of the import you can also have #<<INCLUDE_ANSIBLE_MODULE_COMMON>>, which will be replaced by from ansible.module_utils.basic import *.)

  2. In fact, as soon as you use module_utils in Python modules, you end up using ansible.module_utils.basic.AnsibleModule indirectly. That code is always pushed to the client and the module parameter values are embedded into it. There doesn’t seem to be a way around this when using Python modules with module utils.

    (I did some experiments back in 2024 / discussion - without success.)

  3. Do not include anything from ansible or ansible_collections, and have the string <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> somewhere. Then you get the arguments inserted there as a JSON string and you can do whatever you want (I think). Except importing anything from a collection…

  4. Do not include anything from ansible or ansible_collections, and have the string WANT_JSON somewhere. Then the code is run with the arguments passed through a file whose path is provided as the first (and only) command line argument, and you are expected to print a JSON result to stdout. The arguments are parsed as a JSON file, so at least they’re easy to parse. But you still cannot import anything from a collection…

  5. Use binary modules. For example, write them in Golang or Rust, and provide them in compiled form. Ansible-core will pass the input to them in JSON, and expects the result to be written back to stdout in JSON. While this certainly works, it isn’t really great either.

This all isn’t appealing.

To make this work, we need a way to handle the module execution ourselves. (I’ve created a proposal for that and started a discussion on it in the forum.) Preferably without having to repeat too much code from ansible-core (like ansiballz). And without having to monkey-patch ansible-core.

There has been talk about maybe having a way to hook into these things (quote, source). But considering what else is happening (and how fast things seem to happen), this probably will take years…

1 Like