diff --git a/changelogs/fragments/nonstandard-distro-fallback.yml b/changelogs/fragments/nonstandard-distro-fallback.yml new file mode 100644 index 00000000000..b4aa515e7b7 --- /dev/null +++ b/changelogs/fragments/nonstandard-distro-fallback.yml @@ -0,0 +1,2 @@ +minor_changes: + - module_utils distro - when a 'distro' package/module is in PYTHONPATH but isn't the real 'distro' package/module that we expect, gracefully fall back to our own bundled distro. diff --git a/lib/ansible/module_utils/distro/__init__.py b/lib/ansible/module_utils/distro/__init__.py index 25b148a1484..52c5344109a 100644 --- a/lib/ansible/module_utils/distro/__init__.py +++ b/lib/ansible/module_utils/distro/__init__.py @@ -32,15 +32,25 @@ _BUNDLED_METADATA = {"pypi_name": "distro", "version": "1.5.0"} import sys +import types try: import distro as _system_distro except ImportError: _system_distro = None +else: + # There could be a 'distro' package/module that isn't what we expect, on the + # PYTHONPATH. Rather than erroring out in this case, just fall back to ours. + # We require more functions than distro.id(), but this is probably a decent + # test that we have something we can reasonably use. + if not hasattr(_system_distro, 'id') or \ + not isinstance(_system_distro.id, types.FunctionType): + _system_distro = None if _system_distro: distro = _system_distro else: # Our bundled copy from ansible.module_utils.distro import _distro as distro + sys.modules['ansible.module_utils.distro'] = distro diff --git a/test/integration/targets/module_utils_distro/aliases b/test/integration/targets/module_utils_distro/aliases new file mode 100644 index 00000000000..0b4d548e4d9 --- /dev/null +++ b/test/integration/targets/module_utils_distro/aliases @@ -0,0 +1 @@ +shippable/posix/group3 \ No newline at end of file diff --git a/test/integration/targets/module_utils_distro/meta/main.yml b/test/integration/targets/module_utils_distro/meta/main.yml new file mode 100644 index 00000000000..1810d4bec98 --- /dev/null +++ b/test/integration/targets/module_utils_distro/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_remote_tmp_dir diff --git a/test/integration/targets/module_utils_distro/runme.sh b/test/integration/targets/module_utils_distro/runme.sh new file mode 100755 index 00000000000..e5d3d05333d --- /dev/null +++ b/test/integration/targets/module_utils_distro/runme.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -eux + +# Ensure that when a non-distro 'distro' package is in PYTHONPATH, we fallback +# to our bundled one. +new_pythonpath="$OUTPUT_DIR/pythonpath" +mkdir -p "$new_pythonpath/distro" +touch "$new_pythonpath/distro/__init__.py" + +export PYTHONPATH="$new_pythonpath:$PYTHONPATH" + +# Sanity test to make sure the above worked +set +e +distro_id_fail="$(python -c 'import distro; distro.id' 2>&1)" +set -e +grep -q "AttributeError:.*has no attribute 'id'" <<< "$distro_id_fail" + +# ansible.module_utils.common.sys_info imports distro, and itself gets imported +# in DataLoader, so all we have to do to test the fallback is run `ansible`. +ansirun="$(ansible -i ../../inventory -a "echo \$PYTHONPATH" localhost)" +grep -q "$new_pythonpath" <<< "$ansirun" + +rm -rf "$new_pythonpath"