Problem upgrading to Ansible 2.19

Hello,

I am trying to build a new EE with Ansible 2.19.

  • Is there a minimal Python version requirement for Ansible 2.19? I couldn’t find one. I need the Azure collection, and aumqp (one of the dependencies) fails to build with Python 3.14, so I’m using Python 3.12 at the moment.

I changed the EE definition to use 2.19:

ansible_core:
    package_pip: ansible-core<2.20

(from <2.19). Nothing else changed in the EE definition.

I now get an error ModuleNotFoundError: No module named 'ansible.module_utils.common.sentinel' when running a playbook. Searching for this error gave me zero results, so I have no idea where to start finding out what’s going wrong.

Full stack trace:

  Traceback (most recent call last):
    File "/tmp/ansible_Testplaybook_payload_csu_j205/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_ansiballz/_loader.py", line 35, in run_module
    File "/tmp/ansible_Testplaybook_payload_csu_j205/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_ansiballz/_loader.py", line 62, in _run_module
    File "<frozen runpy>", line 226, in run_module
    File "<frozen runpy>", line 98, in _run_module_code
    File "<frozen runpy>", line 88, in _run_code
    File "/tmp/ansible_Testplaybook_payload_csu_j205/ansible_Testplaybook_payload.zip/ansible/legacy/Testplaybook.py", line 72, in <module>
    File "/usr/local/lib/python3.12/site-packages/ansible/errors/__init__.py", line 16, in <module>
      from .._internal._errors import _error_utils
    File "/usr/local/lib/python3.12/site-packages/ansible/_internal/_errors/_error_utils.py", line 13, in <module>
      from ansible.module_utils._internal import _ambient_context, _event_utils, _messages, _traceback
  ImportError: cannot import name '_ambient_context' from
  'ansible.module_utils._internal'
  (/tmp/ansible_Testplaybook_payload_csu_j205/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/__init__.py)


  During handling of the above exception, another exception occurred:

  Traceback (most recent call last):
    File "/runner/.ansible/tmp/ansible-tmp-1768991758.9563625-22-263207363273348/AnsiballZ_Testplaybook.py", line 266, in <module>
      _ansiballz_main(
    File "/runner/.ansible/tmp/ansible-tmp-1768991758.9563625-22-263207363273348/AnsiballZ_Testplaybook.py", line 260, in _ansiballz_main
      invoke_module(zipped_mod, encoded_params)
    File "/runner/.ansible/tmp/ansible-tmp-1768991758.9563625-22-263207363273348/AnsiballZ_Testplaybook.py", line 140, in invoke_module
      _loader.run_module(
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_ansiballz/_loader.py", line 43, in run_module
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_ansiballz/_loader.py", line 77, in _handle_exception
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/common/json.py", line 85, in get_module_encoder
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/common/json.py", line 75, in get_encoder
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_json/__init__.py", line 18, in get_encoder_decoder
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_json/__init__.py", line 39, in get_serialization_module
    File "/usr/lib64/python3.12/importlib/__init__.py", line 90, in import_module
      return _bootstrap._gcd_import(name[level:], package, level)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
    File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
    File "<frozen importlib._bootstrap_external>", line 999, in exec_module
    File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py", line 11, in <module>
    File "/tmp/ansible_Testplaybook_payload_n7ywkp0a/ansible_Testplaybook_payload.zip/ansible/module_utils/_internal/_json/_profiles/__init__.py", line 268, in __init_subclass__
    File "/usr/local/lib/python3.12/site-packages/ansible/_internal/__init__.py", line 15, in get_controller_serialize_map
      from ansible._internal._templating import _lazy_containers
    File "/usr/local/lib/python3.12/site-packages/ansible/_internal/_templating/_lazy_containers.py", line 21, in <module>
      from ansible.utils.sentinel import Sentinel
    File "/usr/local/lib/python3.12/site-packages/ansible/utils/sentinel.py", line 8, in <module>
      from ansible.module_utils.common.sentinel import Sentinel  # pylint: disable=unused-import
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ModuleNotFoundError: No module named 'ansible.module_utils.common.sentinel'

Version info:

ansible-playbook [core 2.19.5]
  config file = /runner/project/ansible.cfg
  configured module search path = ['/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules', '/runner/project/plugins/modules']
  ansible python module location = /usr/local/lib/python3.12/site-packages/ansible
  ansible collection location = /runner/requirements_collections:/runner/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible-playbook
  python version = 3.12.12 (main, Jan  8 2026, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-14)] (/usr/bin/python3.12)
  jinja version = 3.1.6
  pyyaml version = 6.0.3 (with libyaml v0.2.5)
Using /runner/project/ansible.cfg as config file

Anyone has an idea what might go wrong?

My two cents, as I’m not certain what the problem could be.

As far as i can find, Python >=3.11 works with Ansible 2.19.

My first thought would be that either the module has been deprecated, or that the name has changed over time, but that’s just me guessing.

Module is still available: ansible/lib/ansible/module_utils/common/sentinel.py at devel · ansible/ansible · GitHub

I missed a part of the trace.

The error seems to be in line 72 of my script, which is:
from ansible.errors import AnsibleError

which results in: ImportError: cannot import name ‘_ambient_context’ from ‘ansible.module_utils._internal’

it seems you have ‘mixed’ ansible installations, running 2.19 but using lib/ansible from a previous version

There is: it requires Python 3.11+ on the controller. You can find a good overview of Python and PowerShell requirements and useful dates for all versions since 2.9 here: Releases and maintenance — Ansible Core Documentation

2 Likes

That’s very strange.

My execution-environment.yml:

---
version: 3
images:
  base_image:
    name: quay.io/centos/centos:stream9
dependencies:
  python_interpreter:
    package_system: python3.12
    python_path: /usr/bin/python3.12
  ansible_core:
    package_pip: ansible-core<2.20
  ansible_runner:
    package_pip: ansible-runner
  galaxy: requirements.yml
  system: bindep.txt
  python: requirements.txt

What’s wrong/missing about this? The requirements/bindep files don’t contain anything related to ansible itself.

bindep.txt:

cmake [platform:rpm compile]
epel-release [platform:rpm]
gcc [platform:rpm compile]
gcc-c++ [platform:rpm]
git-core [platform:rpm]
git-lfs [platform:rpm]
krb5-devel [platform:rpm compile]
krb5-workstation [platform:rpm]
libcurl-devel [platform:rpm compile]
libpq-devel [platform:rpm]
make [platform:rpm]
openssl-devel [platform:rpm compile]
podman-remote [platform:rpm]
python-unversioned-command [platform:rpm]
python3.12-devel [platform:rpm compile]
python3.12-psycopg2 [platform:rpm]
redhat-rpm-config [platform:rpm]
rsync [platform:rpm]
sshpass [platform:rpm]
subversion [platform:rpm]
unzip [platform:rpm]
zip [platform:rpm]

requirements.txt:

adal
bs4
cryptography
git+https://github.com/ansible/ansible-sign
ncclient
openpyxl
paramiko
pexpect>=4.5
phonenumbers
pickledb
publicsuffixlist
pyOpenSSL
pykerberos
pypsrp[kerberos,credssp]
python-daemon
pywinrm
pywinrm[kerberos,credssp]
pyyaml
receptorctl
requests
requests-credssp
six
toml
unidecode
xlsxwriter
zeep
meraki
msrestazure
ntc_templates
pynetbox

requirements.txt:

---
collections:
  - name: ansible.netcommon
  - name: ansible.posix
  - name: ansible.utils
  - name: ansible.windows
  - name: awx.awx
  - name: azure.azcollection
  - name: cisco.asa
  - name: cisco.ios
  - name: cisco.meraki
  - name: community.azure
  - name: community.crypto
  - name: community.general
  - name: community.network
  - name: community.windows
  - name: fortinet.fortios
  - name: microsoft.ad
  - name: netbox.netbox

I tried building that EE and it worked fine. I was able to run a simple playbook with ansible-navigator:

- hosts: localhost
  tasks:
    - debug:
        msg: hello

Simle test jobs worked fine in my EE as well, but once I started doing fancy stuff like from ansible.errors import AnsibleError the excrement hit the rotating equipment.

Do you have a simple reproducer? from ansible.errors import AnsibleError is not something you can do directly from a playbook. Are you writing own plugins?

Test playbook:

---
- name: Testmodule
  hosts: localhost
  gather_facts: false

  tasks:
    - name: A simple module
      testmodule:
        fail: False

    - name: A simple module
      testmodule:
        fail: True

testmodule.py:

from ansible.errors import AnsibleError
from ansible.module_utils.basic import AnsibleModule


def run_module():
    module_args = dict(fail=dict(type="bool", required=True))

    # Module definitie, inclusief verplichte en afhankelijke parameters
    module = AnsibleModule(
        argument_spec=module_args,
    )

    # Resultaatobject
    result = dict(changed=False)
    if module.params["fail"]:
        module.fail_json("Test job fails", **result)
    module.exit_json(**result)


def main():
    run_module()


if __name__ == "__main__":
    main()

If I comment out the first line, the playbook works. If I add the import, the playbook stops with the ImportError: cannot import name '_ambient_context' from 'ansible.module_utils._internal' error.

When I run the EE locally (docker run -it image bash), and start /usr/bin/python3.12, I can successfully run ‘from ansible.errors import AnsibleError’. It’s just when it’s done from within a module in a playbook started in AWX that it fails.

Remove that import line from your module. Modules should not import from the ansible package, except for module_utils.

1 Like

This has always worked since I started using Ansible (around 4 or 5 years ago).
As far as I know you can use all modules that are installed on the machine that runs the code, and Ansible will also copy the module_utils modules to the machine that runs the code. Since the code is running on localhost, I should be able to use the ansible module itself.

I found the following:

Ansible will only transfer referenced module_util codes from either ansible or ansible_collections to the remote. Anything else needs to be installed as a normal Python module on the remote to be used.

When checking with ‘pip list’ I see that ansible-core 2.19.5 is installed:

{
  "name": "ansible-core", 
  "version": "2.19.5",
  "location": "/usr/local/lib/python3.12/site-packages",
  "installer": "pip"
}

So I should be able to use this Python module. Most things work (e.g. from ansible.plugins.lookup import LookupBase, or from ansible.utils.display import Display) but the error import fails.

See Need explanation that modules can only import from `module_utils` within the `ansible` namespace · Issue #3442 · ansible/ansible-documentation · GitHub for more info.

I understand the rationale behind ‘you should only include from ansible.module_utils, because we won’t copy everything to the remote node’, but the ‘remote node’ is localhost, so the ansible Python module is installed. I checked os.path in a module, and /usr/local/lib/python3.12/dist-packages is there.

Oh well. I’ll rewrite everything to use something else than AnsibleError…

FWIW:
python3 'path/to/module-with-errorimport' ansibleargs.json works with from ansible.errors import AnsibleError.

If you’re writing a collection, I’d suggest to run ansible-test sanity on it. It would have warned you about this.

Importing ansible.errors doesn’t fail because the ansible Python module isn’t installed. It fails because testmodule doesn’t have access to the ansible.module_utils that ansible.errors needs. To include those in the zipfile, you’d need to add imports for them.

Oh well. I’ll rewrite everything to use something else than AnsibleError…

Emit errors using JSON (for example, using AnsibleModule.fail_json()), or you could use your own exception.

Or, why not write an action plugin, lookup plugin, or filter/test plugin, which run on the control node by design?

  • I install python module datetime’. I can use from datetime.datetime include X in my code.
  • I install python module ‘requests’, I can include from requests include X in my code.
  • I install python module ‘ansible’, I can not from ansible.errors include Y in my code.

Sorry, I don’t see why. Including ansible.errors on a node that has the ansible module installed has always worked, until 2.19.
That’s a breaking change, and should have been mentioned in the release notes.

Sorry, I don’t see why. Including ansible.errors on a node that has the ansible module installed has always worked, until 2.19.

I explained why. Look at the traceback, it is NOT complaining it can’t find ansible.errors, it’s complaining about module_utils that are not packaged in the zipfile. From within the module context, you have a subset of module_utils available (controlled by explicitly importing things in ansible.module_utils). You’d need to add the following imports to your module to make sure the zipfile includes everything necessary:

from ansible.module_utils.common.sentinel import Sentinel
from ansible.module_utils._internal import _ambient_context  # Note: anything named with a leading underscore is private, and can break, be renamed, etc at any time
from ansible.module_utils.common import yaml

You can expect this kind of thing to keep “breaking” because it’s not public API. Follow the docs link above for more clarification.

2 Likes