Ansible playbook run fails due to Python version problem: How to force a Python version on the client

Written by - 4 comments

Published on September 10th 2020 - Listed in Ansible Linux Python Personal

When a (pretty basic) Ansible playbook was run against a SLES 15 SP1 client, the playbook already stopped after the first few tasks with a Python related error message:

TASK [MONI - Install Nagios packages (SLES10/11)] ******************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ImportError: No module named xml
failed: [sles15.local] (item=[u'nrpe', u'nagios-plugins-all', u'ksh', u'sysstat']) => {"ansible_loop_var": "item", "changed": false, "item": ["nrpe", "nagios-plugins-all", "ksh", "sysstat"], "module_stderr": "Shared connection to closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n  File \"/home/ansible/.ansible/tmp/ansible-tmp-1599726118.74-10511-262880492763867/\", line 102, in \r\n    _ansiballz_main()\r\n  File \"/home/ansible/.ansible/tmp/ansible-tmp-1599726118.74-10511-262880492763867/\", line 94, in _ansiballz_main\r\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n  File \"/home/ansible/.ansible/tmp/ansible-tmp-1599726118.74-10511-262880492763867/\", line 40, in invoke_module\r\n    runpy.run_module(mod_name='ansible.modules.packaging.os.zypper', init_globals=None, run_name='__main__', alter_sys=True)\r\n  File \"/usr/lib64/python2.7/\", line 188, in run_module\r\n    fname, loader, pkg_name)\r\n  File \"/usr/lib64/python2.7/\", line 82, in _run_module_code\r\n    mod_name, mod_fname, mod_loader, pkg_name)\r\n  File \"/usr/lib64/python2.7/\", line 72, in _run_code\r\n    exec code in run_globals\r\n  File \"/tmp/ansible_zypper_payload_cpZrw_/\", line 195, in \r\nImportError: No module named xml\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

PLAY RECAP ******************************************************************************************
sles15.local    : ok=1    changed=0    unreachable=0    failed=1    skipped=7    rescued=0    ignored=0

On the SLES 15 machine, this can quickly be verified by manually trying to import the xml module into Python:

sles15:~ # python
Python 2.7.17 (default, Nov 04 2019, 16:08:54) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import xml
Traceback (most recent call last):
  File "", line 1, in
ImportError: No module named xml

Obviously an older Python 2.7 is used here as default Python version - even though Python 3.6 is installed at the same time.

Install the missing Python library using zypper

The obvious solution is to simply install the missing module/library from the package manager (zypper). However when looking for the relevant package (libxml2-python), only the source package python-libxml2-python was available for Python 2 - while the "normal" package python3-libxml2-python was available for Python 3:

sles15:~ # zypper se libxml2-python
Refreshing service 'Basesystem_Module_15_SP1_x86_64'.
Refreshing service 'SUSE_Linux_Enterprise_Server_15_SP1_x86_64'.
Refreshing service 'SUSE_Package_Hub_15_SP1_x86_64'.
Refreshing service 'Server_Applications_Module_15_SP1_x86_64'.
Loading repository data...
Reading installed packages...

S  | Name                   | Summary                     | Type
   | python-libxml2-python  | Python Bindings for libxml2 | srcpackage
   | python3-libxml2-python | Python Bindings for libxml2 | package

Installing the python3-libxml2-python package was easy:

sles15:~ # zypper in python3-libxml2-python
Refreshing service 'Basesystem_Module_15_SP1_x86_64'.
Refreshing service 'SUSE_Linux_Enterprise_Server_15_SP1_x86_64'.
Refreshing service 'SUSE_Package_Hub_15_SP1_x86_64'.
Refreshing service 'Server_Applications_Module_15_SP1_x86_64'.
Loading repository data...
Reading installed packages...
Resolving package dependencies...

The following NEW package is going to be installed:

1 new package to install.
Overall download size: 221.0 KiB. Already cached: 0 B. After the operation, additional 1.6 MiB will be used.
Continue? [y/n/v/...? shows all options] (y): y
Retrieving package python3-libxml2-python-2.9.7-3.22.1.x86_64                                          (1/1), 221.0 KiB (  1.6 MiB unpacked)
Retrieving: python3-libxml2-python-2.9.7-3.22.1.x86_64.rpm ........................[done]
Checking for file conflicts: ......................................................[done]
(1/1) Installing: python3-libxml2-python-2.9.7-3.22.1.x86_64 ......................[done]

And importing the xml module in Python 3 was working, too:

sles15:~ # python3
Python 3.6.10 (default, Dec 19 2019, 15:48:40) [GCC] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import xml
>>> exit()

However the Ansible playbook run still failed due to the same error message. Obviously Python 2 is used by default, as python on this SLES 15 machine points to Python 2:

sles15:~ # ll /usr/bin/python
lrwxrwxrwx 1 root root 9 May  4 18:01 /usr/bin/python -> python2.7

And the missing libxml2 library could not be installed for Python 2 on this SLES 15 machine...

A possible quick and dirty fix would be to change the symlink to point to /usr/bin/python3 - but maybe other scripts are running on this SLES 15 machine which rely on Python 2 and this could potentially break a lot of things. But there's a better way!

Forcing the Python version in Ansible inventory

Ansible can be told which Python version should be used on the client. By using the setting ansible_python_interpreter, a specific Python path/version can be defined on a per client basis (hint found in Ansible issue 59686):

ansible@ansibleserver:~$ grep -h sles15 inventory/*
sles15.local ansible_ssh_host= ansible_python_interpreter=/usr/bin/python3

The playbook now runs with Python 3 on the SLES 15 machine and finally finished with success:

PLAY RECAP *****************************************************************************************
sles15.local    : ok=14   changed=6    unreachable=0    failed=0    skipped=24   rescued=0    ignored=0

In other news: This is blog post 1000!!!

Wow. The counter increased to a four digit number now! This is my 1000th blog post! It took me more than 12 years to achieve this number. The first blog post (Welcome to was published on May 26th 2008. A long time ago. Back then virtual machines was the current and still upcoming hype. Nobody talked of containers let alone Kubernetes back then.

Blog post 1000! This is cool!

But not only technical stuff surrounding me changed - in the meantime I married and became a father or two wonderful (most of the times anyway) kids. Besides this I co-founded Infiniroot where customer tasks and project have a priority. This clearly takes a lot of my time.

Will there be a 2000th entry in the future? Only time will tell. But it will clearly be difficult. But as long as there are still challenges left to resolve (oh yes, there will), new blog articles will continue to roll in.

Add a comment

Show form to leave a comment

Comments (newest first)

Rafael from São Paulo - Brazil wrote on Sep 11th, 2020:

By the way, man. Congratz on the post 1000.

Rafael from wrote on Sep 10th, 2020:

Hi Claudio. It was the same error that you have shared. For me was necessary to install the python-xml package too. Now it is working! Thank you, man.

ck from Switzerland wrote on Sep 10th, 2020:

Hi Rafael. So what's the exact error? Also ImportError on xml? Are you sure the Ansible client is running on Python 3? Is this a SLES15, too?

Rafael from wrote on Sep 10th, 2020:

Mna, same error here, and to install the xml didn't work.