From 8d1cf7f266d7a81c248838917f8df5475e4439b9 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 11 May 2021 12:33:51 -0500 Subject: [PATCH] Vendor `distutils.version` (#74644) * Vendor distutils.version * Fix import order. ci_complete * remove distutils warning filter * Don't remove warnings filter from importer * ci_complete * Add pylint config for preventing distutils.version * Add changelog fragment --- .../fragments/74599-vendor-distutils.yml | 3 + .../command_plugins/dump_keywords.py | 2 +- .../command_plugins/release_announcement.py | 2 +- lib/ansible/executor/interpreter_discovery.py | 2 +- .../executor/powershell/module_manifest.py | 2 +- lib/ansible/galaxy/collection/__init__.py | 2 +- .../dependency_resolution/versioning.py | 2 +- lib/ansible/galaxy/role.py | 2 +- lib/ansible/module_utils/compat/version.py | 343 ++++++++++++++++++ .../module_utils/facts/system/service_mgr.py | 2 +- lib/ansible/modules/dnf.py | 2 +- lib/ansible/modules/git.py | 2 +- lib/ansible/modules/iptables.py | 2 +- lib/ansible/modules/pip.py | 2 +- lib/ansible/modules/service.py | 2 +- lib/ansible/modules/subversion.py | 2 +- .../plugins/connection/paramiko_ssh.py | 2 +- lib/ansible/plugins/test/core.py | 2 +- lib/ansible/template/__init__.py | 2 +- lib/ansible/utils/version.py | 2 +- test/integration/targets/subversion/runme.sh | 2 +- .../sanity/code-smell/runtime-metadata.py | 12 +- .../_data/sanity/import/importer.py | 1 + .../sanity/pylint/config/ansible-test.cfg | 5 + .../_data/sanity/pylint/config/default.cfg | 5 + .../_data/sanity/pylint/config/sanity.cfg | 5 + .../_data/sanity/pylint/plugins/deprecated.py | 3 +- .../validate-modules/validate_modules/main.py | 2 +- .../validate_modules/schema.py | 2 +- test/sanity/code-smell/deprecated-config.py | 2 +- test/sanity/code-smell/update-bundled.py | 2 +- .../integration/plugins/inventory/foreman.py | 2 +- .../plugins/module_utils/aws/core.py | 2 +- .../plugins/module_utils/crypto.py | 2 +- .../plugins/module_utils/docker/common.py | 2 +- .../plugins/module_utils/postgres.py | 2 +- .../integration/plugins/modules/ec2.py | 2 +- .../integration/plugins/modules/htpasswd.py | 2 +- .../plugins/modules/mongodb_user.py | 2 +- .../integration/plugins/modules/x509_crl.py | 2 +- .../plugins/modules/x509_crl_info.py | 2 +- test/units/utils/test_version.py | 2 +- 42 files changed, 398 insertions(+), 47 deletions(-) create mode 100644 changelogs/fragments/74599-vendor-distutils.yml create mode 100644 lib/ansible/module_utils/compat/version.py diff --git a/changelogs/fragments/74599-vendor-distutils.yml b/changelogs/fragments/74599-vendor-distutils.yml new file mode 100644 index 00000000000..43fe37d73c5 --- /dev/null +++ b/changelogs/fragments/74599-vendor-distutils.yml @@ -0,0 +1,3 @@ +minor_changes: +- Vendor ``distutils.version`` due to it's deprecation in Python 3.10 and impending removal in Python 3.12 + (https://github.com/ansible/ansible/issues/74599) diff --git a/hacking/build_library/build_ansible/command_plugins/dump_keywords.py b/hacking/build_library/build_ansible/command_plugins/dump_keywords.py index 2fc6e5d259f..481df411b49 100644 --- a/hacking/build_library/build_ansible/command_plugins/dump_keywords.py +++ b/hacking/build_library/build_ansible/command_plugins/dump_keywords.py @@ -10,7 +10,7 @@ import importlib import os.path import pathlib import re -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion import jinja2 import yaml diff --git a/hacking/build_library/build_ansible/command_plugins/release_announcement.py b/hacking/build_library/build_ansible/command_plugins/release_announcement.py index 620dda0d720..edc928a0ec9 100644 --- a/hacking/build_library/build_ansible/command_plugins/release_announcement.py +++ b/hacking/build_library/build_ansible/command_plugins/release_announcement.py @@ -9,7 +9,7 @@ __metaclass__ = type import sys from collections import UserString -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion # Pylint doesn't understand Python3 namespace modules. from ..commands import Command # pylint: disable=relative-beyond-top-level diff --git a/lib/ansible/executor/interpreter_discovery.py b/lib/ansible/executor/interpreter_discovery.py index 218920a1228..28158aedb38 100644 --- a/lib/ansible/executor/interpreter_discovery.py +++ b/lib/ansible/executor/interpreter_discovery.py @@ -14,7 +14,7 @@ from ansible.module_utils._text import to_native, to_text from ansible.module_utils.distro import LinuxDistribution from ansible.utils.display import Display from ansible.utils.plugin_docs import get_versioned_doclink -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from traceback import format_exc display = Display() diff --git a/lib/ansible/executor/powershell/module_manifest.py b/lib/ansible/executor/powershell/module_manifest.py index a784d244b09..b8aaeb7b812 100644 --- a/lib/ansible/executor/powershell/module_manifest.py +++ b/lib/ansible/executor/powershell/module_manifest.py @@ -12,7 +12,7 @@ import pkgutil import random import re -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible import constants as C from ansible.errors import AnsibleError diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index b6b7c9b340d..f13f5e4159e 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -22,7 +22,7 @@ import yaml from collections import namedtuple from contextlib import contextmanager -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from hashlib import sha256 from io import BytesIO from itertools import chain diff --git a/lib/ansible/galaxy/dependency_resolution/versioning.py b/lib/ansible/galaxy/dependency_resolution/versioning.py index c57f0d21e9f..7dcec7780ff 100644 --- a/lib/ansible/galaxy/dependency_resolution/versioning.py +++ b/lib/ansible/galaxy/dependency_resolution/versioning.py @@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import operator -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.utils.version import SemanticVersion diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index 2c072a6fd59..9868f9ff54c 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -27,7 +27,7 @@ import datetime import os import tarfile import tempfile -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from shutil import rmtree from ansible import context diff --git a/lib/ansible/module_utils/compat/version.py b/lib/ansible/module_utils/compat/version.py new file mode 100644 index 00000000000..59ee9dbc013 --- /dev/null +++ b/lib/ansible/module_utils/compat/version.py @@ -0,0 +1,343 @@ +# Vendored copy of distutils/version.py from CPython 3.9.5 +# +# Implements multiple version numbering conventions for the +# Python Module Distribution Utilities. +# +# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0) +# + +"""Provides classes to represent module version numbers (one class for +each style of version numbering). There are currently two such classes +implemented: StrictVersion and LooseVersion. + +Every version number class implements the following interface: + * the 'parse' method takes a string and parses it to some internal + representation; if the string is an invalid version number, + 'parse' raises a ValueError exception + * the class constructor takes an optional string argument which, + if supplied, is passed to 'parse' + * __str__ reconstructs the string that was passed to 'parse' (or + an equivalent string -- ie. one that will generate an equivalent + version number instance) + * __repr__ generates Python code to recreate the version number instance + * _cmp compares the current instance with either another instance + of the same class or a string (which will be parsed to an instance + of the same class, thus must follow the same rules) +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +try: + RE_FLAGS = re.VERBOSE | re.ASCII +except AttributeError: + RE_FLAGS = re.VERBOSE + + +class Version: + """Abstract base class for version numbering classes. Just provides + constructor (__init__) and reproducer (__repr__), because those + seem to be the same for all version numbering classes; and route + rich comparisons to _cmp. + """ + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def __repr__(self): + return "%s ('%s')" % (self.__class__.__name__, str(self)) + + def __eq__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c == 0 + + def __lt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c < 0 + + def __le__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c <= 0 + + def __gt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c > 0 + + def __ge__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c >= 0 + + +# Interface for version-number classes -- must be implemented +# by the following classes (the concrete ones -- Version should +# be treated as an abstract class). +# __init__ (string) - create and take same action as 'parse' +# (string parameter is optional) +# parse (string) - convert a string representation to whatever +# internal representation is appropriate for +# this style of version numbering +# __str__ (self) - convert back to a string; should be very similar +# (if not identical to) the string supplied to parse +# __repr__ (self) - generate Python code to recreate +# the instance +# _cmp (self, other) - compare two version numbers ('other' may +# be an unparsed version string, or another +# instance of your version class) + + +class StrictVersion(Version): + """Version numbering for anal retentives and software idealists. + Implements the standard interface for version number classes as + described above. A version number consists of two or three + dot-separated numeric components, with an optional "pre-release" tag + on the end. The pre-release tag consists of the letter 'a' or 'b' + followed by a number. If the numeric components of two version + numbers are equal, then one with a pre-release tag will always + be deemed earlier (lesser) than one without. + + The following are valid version numbers (shown in the order that + would be obtained by sorting according to the supplied cmp function): + + 0.4 0.4.0 (these two are equivalent) + 0.4.1 + 0.5a1 + 0.5b3 + 0.5 + 0.9.6 + 1.0 + 1.0.4a3 + 1.0.4b1 + 1.0.4 + + The following are examples of invalid version numbers: + + 1 + 2.7.2.2 + 1.3.a4 + 1.3pl1 + 1.3c4 + + The rationale for this version numbering system will be explained + in the distutils documentation. + """ + + version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', + RE_FLAGS) + + def parse(self, vstring): + match = self.version_re.match(vstring) + if not match: + raise ValueError("invalid version number '%s'" % vstring) + + (major, minor, patch, prerelease, prerelease_num) = \ + match.group(1, 2, 4, 5, 6) + + if patch: + self.version = tuple(map(int, [major, minor, patch])) + else: + self.version = tuple(map(int, [major, minor])) + (0,) + + if prerelease: + self.prerelease = (prerelease[0], int(prerelease_num)) + else: + self.prerelease = None + + def __str__(self): + if self.version[2] == 0: + vstring = '.'.join(map(str, self.version[0:2])) + else: + vstring = '.'.join(map(str, self.version)) + + if self.prerelease: + vstring = vstring + self.prerelease[0] + str(self.prerelease[1]) + + return vstring + + def _cmp(self, other): + if isinstance(other, str): + other = StrictVersion(other) + elif not isinstance(other, StrictVersion): + return NotImplemented + + if self.version != other.version: + # numeric versions don't match + # prerelease stuff doesn't matter + if self.version < other.version: + return -1 + else: + return 1 + + # have to compare prerelease + # case 1: neither has prerelease; they're equal + # case 2: self has prerelease, other doesn't; other is greater + # case 3: self doesn't have prerelease, other does: self is greater + # case 4: both have prerelease: must compare them! + + if (not self.prerelease and not other.prerelease): + return 0 + elif (self.prerelease and not other.prerelease): + return -1 + elif (not self.prerelease and other.prerelease): + return 1 + elif (self.prerelease and other.prerelease): + if self.prerelease == other.prerelease: + return 0 + elif self.prerelease < other.prerelease: + return -1 + else: + return 1 + else: + raise AssertionError("never get here") + +# end class StrictVersion + +# The rules according to Greg Stein: +# 1) a version number has 1 or more numbers separated by a period or by +# sequences of letters. If only periods, then these are compared +# left-to-right to determine an ordering. +# 2) sequences of letters are part of the tuple for comparison and are +# compared lexicographically +# 3) recognize the numeric components may have leading zeroes +# +# The LooseVersion class below implements these rules: a version number +# string is split up into a tuple of integer and string components, and +# comparison is a simple tuple comparison. This means that version +# numbers behave in a predictable and obvious way, but a way that might +# not necessarily be how people *want* version numbers to behave. There +# wouldn't be a problem if people could stick to purely numeric version +# numbers: just split on period and compare the numbers as tuples. +# However, people insist on putting letters into their version numbers; +# the most common purpose seems to be: +# - indicating a "pre-release" version +# ('alpha', 'beta', 'a', 'b', 'pre', 'p') +# - indicating a post-release patch ('p', 'pl', 'patch') +# but of course this can't cover all version number schemes, and there's +# no way to know what a programmer means without asking him. +# +# The problem is what to do with letters (and other non-numeric +# characters) in a version number. The current implementation does the +# obvious and predictable thing: keep them as strings and compare +# lexically within a tuple comparison. This has the desired effect if +# an appended letter sequence implies something "post-release": +# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". +# +# However, if letters in a version number imply a pre-release version, +# the "obvious" thing isn't correct. Eg. you would expect that +# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison +# implemented here, this just isn't so. +# +# Two possible solutions come to mind. The first is to tie the +# comparison algorithm to a particular set of semantic rules, as has +# been done in the StrictVersion class above. This works great as long +# as everyone can go along with bondage and discipline. Hopefully a +# (large) subset of Python module programmers will agree that the +# particular flavour of bondage and discipline provided by StrictVersion +# provides enough benefit to be worth using, and will submit their +# version numbering scheme to its domination. The free-thinking +# anarchists in the lot will never give in, though, and something needs +# to be done to accommodate them. +# +# Perhaps a "moderately strict" version class could be implemented that +# lets almost anything slide (syntactically), and makes some heuristic +# assumptions about non-digits in version number strings. This could +# sink into special-case-hell, though; if I was as talented and +# idiosyncratic as Larry Wall, I'd go ahead and implement a class that +# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is +# just as happy dealing with things like "2g6" and "1.13++". I don't +# think I'm smart enough to do it right though. +# +# In any case, I've coded the test suite for this module (see +# ../test/test_version.py) specifically to fail on things like comparing +# "1.2a2" and "1.2". That's not because the *code* is doing anything +# wrong, it's because the simple, obvious design doesn't match my +# complicated, hairy expectations for real-world version numbers. It +# would be a snap to fix the test suite to say, "Yep, LooseVersion does +# the Right Thing" (ie. the code matches the conception). But I'd rather +# have a conception that matches common notions about version numbers. + + +class LooseVersion(Version): + """Version numbering for anarchists and software realists. + Implements the standard interface for version number classes as + described above. A version number consists of a series of numbers, + separated by either periods or strings of letters. When comparing + version numbers, the numeric components will be compared + numerically, and the alphabetic components lexically. The following + are all valid version numbers, in no particular order: + + 1.5.1 + 1.5.2b2 + 161 + 3.10a + 8.02 + 3.4j + 1996.07.12 + 3.2.pl0 + 3.1.1.6 + 2g6 + 11g + 0.960923 + 2.2beta29 + 1.13++ + 5.5.kw + 2.0b1pl0 + + In fact, there is no such thing as an invalid version number under + this scheme; the rules for comparison are simple and predictable, + but may not always give the results you want (for some definition + of "want"). + """ + + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + + def __init__(self, vstring=None): + if vstring: + self.parse(vstring) + + def parse(self, vstring): + # I've given up on thinking I can reconstruct the version string + # from the parsed tuple -- so I just store the string here for + # use by __str__ + self.vstring = vstring + components = [x for x in self.component_re.split(vstring) if x and x != '.'] + for i, obj in enumerate(components): + try: + components[i] = int(obj) + except ValueError: + pass + + self.version = components + + def __str__(self): + return self.vstring + + def __repr__(self): + return "LooseVersion ('%s')" % str(self) + + def _cmp(self, other): + if isinstance(other, str): + other = LooseVersion(other) + elif not isinstance(other, LooseVersion): + return NotImplemented + + if self.version == other.version: + return 0 + if self.version < other.version: + return -1 + if self.version > other.version: + return 1 + +# end class LooseVersion diff --git a/lib/ansible/module_utils/facts/system/service_mgr.py b/lib/ansible/module_utils/facts/system/service_mgr.py index dc8df68e599..ae85bfa760f 100644 --- a/lib/ansible/module_utils/facts/system/service_mgr.py +++ b/lib/ansible/module_utils/facts/system/service_mgr.py @@ -32,7 +32,7 @@ from ansible.module_utils.facts.collector import BaseFactCollector # that don't belong on production boxes. Since our Solaris code doesn't # depend on LooseVersion, do not import it on Solaris. if platform.system() != 'SunOS': - from distutils.version import LooseVersion + from ansible.module_utils.compat.version import LooseVersion class ServiceMgrFactCollector(BaseFactCollector): diff --git a/lib/ansible/modules/dnf.py b/lib/ansible/modules/dnf.py index e1851a13573..d5f5d7b958f 100644 --- a/lib/ansible/modules/dnf.py +++ b/lib/ansible/modules/dnf.py @@ -333,7 +333,7 @@ import sys from ansible.module_utils._text import to_native, to_text from ansible.module_utils.urls import fetch_file from ansible.module_utils.six import PY2, text_type -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module diff --git a/lib/ansible/modules/git.py b/lib/ansible/modules/git.py index f5b63f94e3b..f161836d5fa 100644 --- a/lib/ansible/modules/git.py +++ b/lib/ansible/modules/git.py @@ -321,7 +321,7 @@ import stat import sys import shutil import tempfile -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six import b, string_types diff --git a/lib/ansible/modules/iptables.py b/lib/ansible/modules/iptables.py index 03122e07ee5..760fe11416b 100644 --- a/lib/ansible/modules/iptables.py +++ b/lib/ansible/modules/iptables.py @@ -510,7 +510,7 @@ EXAMPLES = r''' import re -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule diff --git a/lib/ansible/modules/pip.py b/lib/ansible/modules/pip.py index 420e3a18fc7..17da9dbeb58 100644 --- a/lib/ansible/modules/pip.py +++ b/lib/ansible/modules/pip.py @@ -264,7 +264,7 @@ import tempfile import operator import shlex import traceback -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion SETUPTOOLS_IMP_ERR = None try: diff --git a/lib/ansible/modules/service.py b/lib/ansible/modules/service.py index 056183be05c..ce0b2a6e2a5 100644 --- a/lib/ansible/modules/service.py +++ b/lib/ansible/modules/service.py @@ -144,7 +144,7 @@ import time # that don't belong on production boxes. Since our Solaris code doesn't # depend on LooseVersion, do not import it on Solaris. if platform.system() != 'SunOS': - from distutils.version import LooseVersion + from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils.basic import AnsibleModule diff --git a/lib/ansible/modules/subversion.py b/lib/ansible/modules/subversion.py index c28471bf227..348d8d9bc01 100644 --- a/lib/ansible/modules/subversion.py +++ b/lib/ansible/modules/subversion.py @@ -128,7 +128,7 @@ RETURN = r'''#''' import os import re -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule diff --git a/lib/ansible/plugins/connection/paramiko_ssh.py b/lib/ansible/plugins/connection/paramiko_ssh.py index 45f23b4ae12..24362135941 100644 --- a/lib/ansible/plugins/connection/paramiko_ssh.py +++ b/lib/ansible/plugins/connection/paramiko_ssh.py @@ -138,7 +138,7 @@ import sys import re from termios import tcflush, TCIFLUSH -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from binascii import hexlify from ansible.errors import ( diff --git a/lib/ansible/plugins/test/core.py b/lib/ansible/plugins/test/core.py index 4b3995dd16c..e131dc01e10 100644 --- a/lib/ansible/plugins/test/core.py +++ b/lib/ansible/plugins/test/core.py @@ -21,7 +21,7 @@ __metaclass__ = type import re import operator as py_operator -from distutils.version import LooseVersion, StrictVersion +from ansible.module_utils.compat.version import LooseVersion, StrictVersion from ansible import errors from ansible.module_utils._text import to_native, to_text diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index f2946456469..58da28df9b1 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -28,7 +28,7 @@ import re import time from contextlib import contextmanager -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from numbers import Number from traceback import format_exc diff --git a/lib/ansible/utils/version.py b/lib/ansible/utils/version.py index d69723b4731..c045e7d1c86 100644 --- a/lib/ansible/utils/version.py +++ b/lib/ansible/utils/version.py @@ -7,7 +7,7 @@ __metaclass__ = type import re -from distutils.version import LooseVersion, Version +from ansible.module_utils.compat.version import LooseVersion, Version from ansible.module_utils.six import text_type diff --git a/test/integration/targets/subversion/runme.sh b/test/integration/targets/subversion/runme.sh index f505e58168c..99d56aa79b4 100755 --- a/test/integration/targets/subversion/runme.sh +++ b/test/integration/targets/subversion/runme.sh @@ -22,7 +22,7 @@ ansible-playbook runme.yml "$@" -v --tags tests ansible-playbook runme.yml "$@" --tags warnings 2>&1 | tee out.txt version="$(svn --version -q)" -secure=$(python -c "from distutils.version import LooseVersion; print(LooseVersion('$version') >= LooseVersion('1.10.0'))") +secure=$(python -c "from ansible.module_utils.compat.version import LooseVersion; print(LooseVersion('$version') >= LooseVersion('1.10.0'))") if [[ "${secure}" = "False" ]] && [[ "$(grep -c 'To securely pass credentials, upgrade svn to version 1.10.0' out.txt)" -eq 1 ]]; then echo "Found the expected warning" diff --git a/test/lib/ansible_test/_data/sanity/code-smell/runtime-metadata.py b/test/lib/ansible_test/_data/sanity/code-smell/runtime-metadata.py index 29b4ac4bfa7..8bc5098f203 100755 --- a/test/lib/ansible_test/_data/sanity/code-smell/runtime-metadata.py +++ b/test/lib/ansible_test/_data/sanity/code-smell/runtime-metadata.py @@ -9,17 +9,6 @@ import re import sys import warnings -# Temporary solution for the PEP 632 deprecation warning on Python 3.10. -# This should be removed once distutils.version has been vendored in ansible.module_utils. -# See: https://github.com/ansible/ansible/issues/74599 -# pylint: disable=wrong-import-position -warnings.filterwarnings( - 'ignore', - 'The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives', - DeprecationWarning, -) - -from distutils.version import StrictVersion, LooseVersion from functools import partial import yaml @@ -28,6 +17,7 @@ from voluptuous import All, Any, MultipleInvalid, PREVENT_EXTRA from voluptuous import Required, Schema, Invalid from voluptuous.humanize import humanize_error +from ansible.module_utils.compat.version import StrictVersion, LooseVersion from ansible.module_utils.six import string_types from ansible.utils.version import SemanticVersion diff --git a/test/lib/ansible_test/_data/sanity/import/importer.py b/test/lib/ansible_test/_data/sanity/import/importer.py index b72bd2cae39..f1d87200e7e 100755 --- a/test/lib/ansible_test/_data/sanity/import/importer.py +++ b/test/lib/ansible_test/_data/sanity/import/importer.py @@ -532,6 +532,7 @@ def main(): ) # Temporary solution until there is a vendored copy of distutils.version in module_utils. + # Some of our dependencies such as packaging.tags also import distutils, which we have no control over # The warning text is: The distutils package is deprecated and slated for removal in Python 3.12. # Use setuptools or check PEP 632 for potential alternatives warnings.filterwarnings( diff --git a/test/lib/ansible_test/_data/sanity/pylint/config/ansible-test.cfg b/test/lib/ansible_test/_data/sanity/pylint/config/ansible-test.cfg index 3f07497198a..187758f4098 100644 --- a/test/lib/ansible_test/_data/sanity/pylint/config/ansible-test.cfg +++ b/test/lib/ansible_test/_data/sanity/pylint/config/ansible-test.cfg @@ -47,3 +47,8 @@ class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,40}$ attr-rgx=[a-z_][a-z0-9_]{1,40}$ method-rgx=[a-z_][a-z0-9_]{1,40}$ function-rgx=[a-z_][a-z0-9_]{1,40}$ + +[IMPORTS] + +preferred-modules = + distutils.version:ansible.module_utils.compat.version, diff --git a/test/lib/ansible_test/_data/sanity/pylint/config/default.cfg b/test/lib/ansible_test/_data/sanity/pylint/config/default.cfg index c1a08be5ffc..e413151a7b4 100644 --- a/test/lib/ansible_test/_data/sanity/pylint/config/default.cfg +++ b/test/lib/ansible_test/_data/sanity/pylint/config/default.cfg @@ -139,3 +139,8 @@ good-names= ignored-modules= _MovedItems, + +[IMPORTS] + +preferred-modules = + distutils.version:ansible.module_utils.compat.version, diff --git a/test/lib/ansible_test/_data/sanity/pylint/config/sanity.cfg b/test/lib/ansible_test/_data/sanity/pylint/config/sanity.cfg index 77f69b3e220..bcf9549fd7e 100644 --- a/test/lib/ansible_test/_data/sanity/pylint/config/sanity.cfg +++ b/test/lib/ansible_test/_data/sanity/pylint/config/sanity.cfg @@ -48,3 +48,8 @@ good-names= module-rgx=[a-z_][a-z0-9_-]{2,40}$ method-rgx=[a-z_][a-z0-9_]{2,40}$ function-rgx=[a-z_][a-z0-9_]{2,40}$ + +[IMPORTS] + +preferred-modules = + distutils.version:ansible.module_utils.compat.version, diff --git a/test/lib/ansible_test/_data/sanity/pylint/plugins/deprecated.py b/test/lib/ansible_test/_data/sanity/pylint/plugins/deprecated.py index 337ccd75c71..e39e5214bf5 100644 --- a/test/lib/ansible_test/_data/sanity/pylint/plugins/deprecated.py +++ b/test/lib/ansible_test/_data/sanity/pylint/plugins/deprecated.py @@ -7,14 +7,13 @@ __metaclass__ = type import datetime import re -from distutils.version import LooseVersion - import astroid from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.six import string_types from ansible.release import __version__ as ansible_version_raw from ansible.utils.version import SemanticVersion diff --git a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/main.py b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/main.py index 23a279deba1..3237cff40df 100644 --- a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/main.py +++ b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/main.py @@ -33,7 +33,7 @@ import traceback from collections import OrderedDict from contextlib import contextmanager -from distutils.version import StrictVersion, LooseVersion +from ansible.module_utils.compat.version import StrictVersion, LooseVersion from fnmatch import fnmatch import yaml diff --git a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/schema.py b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/schema.py index b335a5da95b..dfce5f9d422 100644 --- a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/schema.py +++ b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/schema.py @@ -8,7 +8,7 @@ __metaclass__ = type import re -from distutils.version import StrictVersion +from ansible.module_utils.compat.version import StrictVersion from functools import partial from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, Required, Schema, Self, ValueInvalid diff --git a/test/sanity/code-smell/deprecated-config.py b/test/sanity/code-smell/deprecated-config.py index 08e93c36596..e8a2d8d4184 100755 --- a/test/sanity/code-smell/deprecated-config.py +++ b/test/sanity/code-smell/deprecated-config.py @@ -25,7 +25,7 @@ import os import re import sys -from distutils.version import StrictVersion +from ansible.module_utils.compat.version import StrictVersion import yaml diff --git a/test/sanity/code-smell/update-bundled.py b/test/sanity/code-smell/update-bundled.py index 3904b7302fd..3f0fa1bee0d 100755 --- a/test/sanity/code-smell/update-bundled.py +++ b/test/sanity/code-smell/update-bundled.py @@ -29,7 +29,7 @@ import fnmatch import json import re import sys -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion import packaging.specifiers diff --git a/test/support/integration/plugins/inventory/foreman.py b/test/support/integration/plugins/inventory/foreman.py index 43073f81ad4..39e0de338b6 100644 --- a/test/support/integration/plugins/inventory/foreman.py +++ b/test/support/integration/plugins/inventory/foreman.py @@ -81,7 +81,7 @@ password: secure validate_certs: False ''' -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.errors import AnsibleError from ansible.module_utils._text import to_bytes, to_native, to_text diff --git a/test/support/integration/plugins/module_utils/aws/core.py b/test/support/integration/plugins/module_utils/aws/core.py index c4527b6deb4..909d0396d4a 100644 --- a/test/support/integration/plugins/module_utils/aws/core.py +++ b/test/support/integration/plugins/module_utils/aws/core.py @@ -65,7 +65,7 @@ import re import logging import traceback from functools import wraps -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion try: from cStringIO import StringIO diff --git a/test/support/integration/plugins/module_utils/crypto.py b/test/support/integration/plugins/module_utils/crypto.py index e67eeff1b42..f3f43f071b3 100644 --- a/test/support/integration/plugins/module_utils/crypto.py +++ b/test/support/integration/plugins/module_utils/crypto.py @@ -31,7 +31,7 @@ __metaclass__ = type import sys -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion try: import OpenSSL diff --git a/test/support/integration/plugins/module_utils/docker/common.py b/test/support/integration/plugins/module_utils/docker/common.py index 03307250d69..08a87702cd8 100644 --- a/test/support/integration/plugins/module_utils/docker/common.py +++ b/test/support/integration/plugins/module_utils/docker/common.py @@ -25,7 +25,7 @@ import platform import re import sys from datetime import timedelta -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib diff --git a/test/support/integration/plugins/module_utils/postgres.py b/test/support/integration/plugins/module_utils/postgres.py index 63811c30559..0ccc6ed7b52 100644 --- a/test/support/integration/plugins/module_utils/postgres.py +++ b/test/support/integration/plugins/module_utils/postgres.py @@ -37,7 +37,7 @@ except ImportError: from ansible.module_utils.basic import missing_required_lib from ansible.module_utils._text import to_native from ansible.module_utils.six import iteritems -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion def postgres_common_argument_spec(): diff --git a/test/support/integration/plugins/modules/ec2.py b/test/support/integration/plugins/modules/ec2.py index 952aa5a1a6f..1e97effd9f5 100644 --- a/test/support/integration/plugins/modules/ec2.py +++ b/test/support/integration/plugins/modules/ec2.py @@ -610,7 +610,7 @@ import time import datetime import traceback from ast import literal_eval -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.ec2 import get_aws_connection_info, ec2_argument_spec, ec2_connect diff --git a/test/support/integration/plugins/modules/htpasswd.py b/test/support/integration/plugins/modules/htpasswd.py index ad12b0c02d8..2c55a6bce1e 100644 --- a/test/support/integration/plugins/modules/htpasswd.py +++ b/test/support/integration/plugins/modules/htpasswd.py @@ -96,7 +96,7 @@ EXAMPLES = """ import os import tempfile import traceback -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils._text import to_native diff --git a/test/support/integration/plugins/modules/mongodb_user.py b/test/support/integration/plugins/modules/mongodb_user.py index 362b3aa45ec..7a18b15908c 100644 --- a/test/support/integration/plugins/modules/mongodb_user.py +++ b/test/support/integration/plugins/modules/mongodb_user.py @@ -200,7 +200,7 @@ user: import os import ssl as ssl_lib import traceback -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from operator import itemgetter try: diff --git a/test/support/integration/plugins/modules/x509_crl.py b/test/support/integration/plugins/modules/x509_crl.py index ef601edadc0..9bb83a5b9f3 100644 --- a/test/support/integration/plugins/modules/x509_crl.py +++ b/test/support/integration/plugins/modules/x509_crl.py @@ -349,7 +349,7 @@ crl: import os import traceback -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils import crypto as crypto_utils from ansible.module_utils._text import to_native, to_text diff --git a/test/support/integration/plugins/modules/x509_crl_info.py b/test/support/integration/plugins/modules/x509_crl_info.py index b61db26ff14..b6d3632074f 100644 --- a/test/support/integration/plugins/modules/x509_crl_info.py +++ b/test/support/integration/plugins/modules/x509_crl_info.py @@ -129,7 +129,7 @@ revoked_certificates: import traceback -from distutils.version import LooseVersion +from ansible.module_utils.compat.version import LooseVersion from ansible.module_utils import crypto as crypto_utils from ansible.module_utils._text import to_native diff --git a/test/units/utils/test_version.py b/test/units/utils/test_version.py index 7d04c112c5a..3c2cbaf4c14 100644 --- a/test/units/utils/test_version.py +++ b/test/units/utils/test_version.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from distutils.version import LooseVersion, StrictVersion +from ansible.module_utils.compat.version import LooseVersion, StrictVersion import pytest