summaryrefslogtreecommitdiffstats
path: root/lib/ansible/module_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:16:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:16:49 +0000
commit48e387c5c12026a567eb7b293a3a590241c0cecb (patch)
tree80f2573be2d7d534b8ac4d2a852fe43f7ac35324 /lib/ansible/module_utils
parentReleasing progress-linux version 2.16.6-1~progress7.99u1. (diff)
downloadansible-core-48e387c5c12026a567eb7b293a3a590241c0cecb.tar.xz
ansible-core-48e387c5c12026a567eb7b293a3a590241c0cecb.zip
Merging upstream version 2.17.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/ansible/module_utils')
-rw-r--r--lib/ansible/module_utils/_text.py3
-rw-r--r--lib/ansible/module_utils/ansible_release.py8
-rw-r--r--lib/ansible/module_utils/api.py3
-rw-r--r--lib/ansible/module_utils/basic.py271
-rw-r--r--lib/ansible/module_utils/common/_collections_compat.py3
-rw-r--r--lib/ansible/module_utils/common/_json_compat.py16
-rw-r--r--lib/ansible/module_utils/common/_utils.py4
-rw-r--r--lib/ansible/module_utils/common/arg_spec.py3
-rw-r--r--lib/ansible/module_utils/common/collections.py3
-rw-r--r--lib/ansible/module_utils/common/dict_transformations.py3
-rw-r--r--lib/ansible/module_utils/common/file.py15
-rw-r--r--lib/ansible/module_utils/common/json.py4
-rw-r--r--lib/ansible/module_utils/common/locale.py3
-rw-r--r--lib/ansible/module_utils/common/network.py5
-rw-r--r--lib/ansible/module_utils/common/parameters.py18
-rw-r--r--lib/ansible/module_utils/common/process.py17
-rw-r--r--lib/ansible/module_utils/common/respawn.py5
-rw-r--r--lib/ansible/module_utils/common/sys_info.py3
-rw-r--r--lib/ansible/module_utils/common/text/converters.py3
-rw-r--r--lib/ansible/module_utils/common/text/formatters.py3
-rw-r--r--lib/ansible/module_utils/common/validation.py11
-rw-r--r--lib/ansible/module_utils/common/warnings.py3
-rw-r--r--lib/ansible/module_utils/common/yaml.py3
-rw-r--r--lib/ansible/module_utils/compat/_selectors2.py655
-rw-r--r--lib/ansible/module_utils/compat/datetime.py4
-rw-r--r--lib/ansible/module_utils/compat/importlib.py34
-rw-r--r--lib/ansible/module_utils/compat/paramiko.py3
-rw-r--r--lib/ansible/module_utils/compat/selectors.py44
-rw-r--r--lib/ansible/module_utils/compat/selinux.py3
-rw-r--r--lib/ansible/module_utils/compat/typing.py3
-rw-r--r--lib/ansible/module_utils/compat/version.py3
-rw-r--r--lib/ansible/module_utils/connection.py3
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Basic.cs23
-rw-r--r--lib/ansible/module_utils/distro/__init__.py7
-rw-r--r--lib/ansible/module_utils/distro/_distro.py464
-rw-r--r--lib/ansible/module_utils/errors.py3
-rw-r--r--lib/ansible/module_utils/facts/__init__.py3
-rw-r--r--lib/ansible/module_utils/facts/ansible_collector.py3
-rw-r--r--lib/ansible/module_utils/facts/collector.py3
-rw-r--r--lib/ansible/module_utils/facts/compat.py3
-rw-r--r--lib/ansible/module_utils/facts/default_collectors.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/aix.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/base.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/darwin.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/dragonfly.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/freebsd.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/hpux.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/hurd.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/linux.py14
-rw-r--r--lib/ansible/module_utils/facts/hardware/netbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/openbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/hardware/sunos.py5
-rw-r--r--lib/ansible/module_utils/facts/namespace.py3
-rw-r--r--lib/ansible/module_utils/facts/network/aix.py3
-rw-r--r--lib/ansible/module_utils/facts/network/base.py3
-rw-r--r--lib/ansible/module_utils/facts/network/darwin.py3
-rw-r--r--lib/ansible/module_utils/facts/network/dragonfly.py3
-rw-r--r--lib/ansible/module_utils/facts/network/fc_wwn.py3
-rw-r--r--lib/ansible/module_utils/facts/network/freebsd.py3
-rw-r--r--lib/ansible/module_utils/facts/network/generic_bsd.py3
-rw-r--r--lib/ansible/module_utils/facts/network/hpux.py3
-rw-r--r--lib/ansible/module_utils/facts/network/hurd.py3
-rw-r--r--lib/ansible/module_utils/facts/network/iscsi.py3
-rw-r--r--lib/ansible/module_utils/facts/network/linux.py3
-rw-r--r--lib/ansible/module_utils/facts/network/netbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/network/nvme.py3
-rw-r--r--lib/ansible/module_utils/facts/network/openbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/network/sunos.py3
-rw-r--r--lib/ansible/module_utils/facts/other/facter.py3
-rw-r--r--lib/ansible/module_utils/facts/other/ohai.py3
-rw-r--r--lib/ansible/module_utils/facts/packages.py3
-rw-r--r--lib/ansible/module_utils/facts/sysctl.py3
-rw-r--r--lib/ansible/module_utils/facts/system/apparmor.py3
-rw-r--r--lib/ansible/module_utils/facts/system/caps.py3
-rw-r--r--lib/ansible/module_utils/facts/system/chroot.py3
-rw-r--r--lib/ansible/module_utils/facts/system/cmdline.py3
-rw-r--r--lib/ansible/module_utils/facts/system/date_time.py3
-rw-r--r--lib/ansible/module_utils/facts/system/distribution.py9
-rw-r--r--lib/ansible/module_utils/facts/system/dns.py3
-rw-r--r--lib/ansible/module_utils/facts/system/env.py3
-rw-r--r--lib/ansible/module_utils/facts/system/fips.py3
-rw-r--r--lib/ansible/module_utils/facts/system/loadavg.py3
-rw-r--r--lib/ansible/module_utils/facts/system/local.py3
-rw-r--r--lib/ansible/module_utils/facts/system/lsb.py3
-rw-r--r--lib/ansible/module_utils/facts/system/pkg_mgr.py37
-rw-r--r--lib/ansible/module_utils/facts/system/platform.py3
-rw-r--r--lib/ansible/module_utils/facts/system/python.py3
-rw-r--r--lib/ansible/module_utils/facts/system/selinux.py3
-rw-r--r--lib/ansible/module_utils/facts/system/service_mgr.py3
-rw-r--r--lib/ansible/module_utils/facts/system/ssh_pub_keys.py3
-rw-r--r--lib/ansible/module_utils/facts/system/user.py3
-rw-r--r--lib/ansible/module_utils/facts/timeout.py3
-rw-r--r--lib/ansible/module_utils/facts/utils.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/base.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/dragonfly.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/freebsd.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/hpux.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/linux.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/netbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/openbsd.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/sunos.py3
-rw-r--r--lib/ansible/module_utils/facts/virtual/sysctl.py3
-rw-r--r--lib/ansible/module_utils/json_utils.py3
-rw-r--r--lib/ansible/module_utils/parsing/convert_bool.py3
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm17
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm12
-rw-r--r--lib/ansible/module_utils/pycompat24.py28
-rw-r--r--lib/ansible/module_utils/service.py36
-rw-r--r--lib/ansible/module_utils/six/__init__.py2
-rw-r--r--lib/ansible/module_utils/splitter.py3
-rw-r--r--lib/ansible/module_utils/urls.py1387
-rw-r--r--lib/ansible/module_utils/yumdnf.py48
112 files changed, 957 insertions, 2472 deletions
diff --git a/lib/ansible/module_utils/_text.py b/lib/ansible/module_utils/_text.py
index f30a5e9..b6dd620 100644
--- a/lib/ansible/module_utils/_text.py
+++ b/lib/ansible/module_utils/_text.py
@@ -1,11 +1,10 @@
# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com> 2016
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
"""
.. warn:: Use ansible.module_utils.common.text.converters instead.
"""
+from __future__ import annotations
# Backwards compat for people still calling it from this package
# pylint: disable=unused-import
diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py
index 60200a0..88f7515 100644
--- a/lib/ansible/module_utils/ansible_release.py
+++ b/lib/ansible/module_utils/ansible_release.py
@@ -15,10 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
-__version__ = '2.16.6'
+__version__ = '2.17.0'
__author__ = 'Ansible, Inc.'
-__codename__ = "All My Love"
+__codename__ = "Gallows Pole"
diff --git a/lib/ansible/module_utils/api.py b/lib/ansible/module_utils/api.py
index 2de8a4e..8f08772 100644
--- a/lib/ansible/module_utils/api.py
+++ b/lib/ansible/module_utils/api.py
@@ -23,8 +23,7 @@ The 'api' module provides the following common argument specs:
- retries: number of attempts
- retry_pause: delay between attempts in seconds
"""
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import copy
import functools
diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py
index 19ca0aa..7308bb7 100644
--- a/lib/ansible/module_utils/basic.py
+++ b/lib/ansible/module_utils/basic.py
@@ -2,22 +2,20 @@
# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com> 2016
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
+import json
import sys
# Used for determining if the system is running a new enough python version
# and should only restrict on our documented minimum versions
-_PY3_MIN = sys.version_info >= (3, 6)
-_PY2_MIN = (2, 7) <= sys.version_info < (3,)
-_PY_MIN = _PY3_MIN or _PY2_MIN
-
-if not _PY_MIN:
- print(
- '\n{"failed": true, '
- '"msg": "ansible-core requires a minimum of Python2 version 2.7 or Python3 version 3.6. Current version: %s"}' % ''.join(sys.version.splitlines())
- )
+_PY_MIN = (3, 7)
+
+if sys.version_info < _PY_MIN:
+ print(json.dumps(dict(
+ failed=True,
+ msg=f"ansible-core requires a minimum of Python version {'.'.join(map(str, _PY_MIN))}. Current version: {''.join(sys.version.splitlines())}",
+ )))
sys.exit(1)
# Ansible modules can be written in any language.
@@ -27,7 +25,6 @@ if not _PY_MIN:
import __main__
import atexit
import errno
-import datetime
import grp
import fcntl
import locale
@@ -36,17 +33,16 @@ import pwd
import platform
import re
import select
+import selectors
import shlex
import shutil
-import signal
import stat
import subprocess
import tempfile
import time
import traceback
-import types
-from itertools import chain, repeat
+from functools import reduce
try:
import syslog
@@ -74,8 +70,6 @@ except ImportError:
# Python2 & 3 way to get NoneType
NoneType = type(None)
-from ansible.module_utils.compat import selectors
-
from ._text import to_native, to_bytes, to_text
from ansible.module_utils.common.text.converters import (
jsonify,
@@ -97,21 +91,9 @@ import hashlib
def _get_available_hash_algorithms():
"""Return a dictionary of available hash function names and their associated function."""
- try:
- # Algorithms available in Python 2.7.9+ and Python 3.2+
- # https://docs.python.org/2.7/library/hashlib.html#hashlib.algorithms_available
- # https://docs.python.org/3.2/library/hashlib.html#hashlib.algorithms_available
- algorithm_names = hashlib.algorithms_available
- except AttributeError:
- # Algorithms in Python 2.7.x (used only for Python 2.7.0 through 2.7.8)
- # https://docs.python.org/2.7/library/hashlib.html#hashlib.hashlib.algorithms
- algorithm_names = set(hashlib.algorithms)
-
algorithms = {}
-
- for algorithm_name in algorithm_names:
+ for algorithm_name in hashlib.algorithms_available:
algorithm_func = getattr(hashlib, algorithm_name, None)
-
if algorithm_func:
try:
# Make sure the algorithm is actually available for use.
@@ -128,12 +110,6 @@ def _get_available_hash_algorithms():
AVAILABLE_HASH_ALGORITHMS = _get_available_hash_algorithms()
-try:
- from ansible.module_utils.common._json_compat import json
-except ImportError as e:
- print('\n{{"msg": "Error: ansible requires the stdlib json: {0}", "failed": true}}'.format(to_native(e)))
- sys.exit(1)
-
from ansible.module_utils.six.moves.collections_abc import (
KeysView,
Mapping, MutableMapping,
@@ -144,19 +120,19 @@ from ansible.module_utils.common.locale import get_best_parsable_locale
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.file import (
_PERM_BITS as PERM_BITS,
- _EXEC_PERM_BITS as EXEC_PERM_BITS,
_DEFAULT_PERM as DEFAULT_PERM,
is_executable,
format_attributes,
get_flags_from_attributes,
FILE_ATTRIBUTES,
+ S_IXANY,
+ S_IRWU_RWG_RWO,
)
from ansible.module_utils.common.sys_info import (
get_distribution,
get_distribution_version,
get_platform_subclass,
)
-from ansible.module_utils.pycompat24 import get_exception, literal_eval
from ansible.module_utils.common.parameters import (
env_fallback,
remove_values,
@@ -167,17 +143,6 @@ from ansible.module_utils.common.parameters import (
)
from ansible.module_utils.errors import AnsibleFallbackNotFound, AnsibleValidationErrorMultiple, UnsupportedError
-from ansible.module_utils.six import (
- PY2,
- PY3,
- b,
- binary_type,
- integer_types,
- iteritems,
- string_types,
- text_type,
-)
-from ansible.module_utils.six.moves import map, reduce, shlex_quote
from ansible.module_utils.common.validation import (
check_missing_parameters,
safe_eval,
@@ -199,24 +164,6 @@ PASSWORD_MATCH = re.compile(r'^(?:.+[-_\s])?pass(?:[-_\s]?(?:word|phrase|wrd|wd)
imap = map
-try:
- # Python 2
- unicode # type: ignore[used-before-def] # pylint: disable=used-before-assignment
-except NameError:
- # Python 3
- unicode = text_type
-
-try:
- # Python 2
- basestring # type: ignore[used-before-def,has-type] # pylint: disable=used-before-assignment
-except NameError:
- # Python 3
- basestring = string_types
-
-_literal_eval = literal_eval
-
-# End of deprecated names
-
# Internal global holding passed in params. This is consulted in case
# multiple AnsibleModules are created. Otherwise each AnsibleModule would
# attempt to read from stdin. Other code should not use this directly as it
@@ -373,33 +320,25 @@ def _load_params():
buffer = fd.read()
fd.close()
else:
- buffer = sys.argv[1]
- if PY3:
- buffer = buffer.encode('utf-8', errors='surrogateescape')
+ buffer = sys.argv[1].encode('utf-8', errors='surrogateescape')
# default case, read from stdin
else:
- if PY2:
- buffer = sys.stdin.read()
- else:
- buffer = sys.stdin.buffer.read()
+ buffer = sys.stdin.buffer.read()
_ANSIBLE_ARGS = buffer
try:
params = json.loads(buffer.decode('utf-8'))
except ValueError:
- # This helper used too early for fail_json to work.
- print('\n{"msg": "Error: Module unable to decode valid JSON on stdin. Unable to figure out what parameters were passed", "failed": true}')
+ # This helper is used too early for fail_json to work.
+ print('\n{"msg": "Error: Module unable to decode stdin/parameters as valid JSON. Unable to parse what parameters were passed", "failed": true}')
sys.exit(1)
- if PY2:
- params = json_dict_unicode_to_bytes(params)
-
try:
return params['ANSIBLE_MODULE_ARGS']
except KeyError:
# This helper does not have access to fail_json so we have to print
# json output on our own.
- print('\n{"msg": "Error: Module unable to locate ANSIBLE_MODULE_ARGS in json data from stdin. Unable to figure out what parameters were passed", '
+ print('\n{"msg": "Error: Module unable to locate ANSIBLE_MODULE_ARGS in JSON data from stdin. Unable to figure out what parameters were passed", '
'"failed": true}')
sys.exit(1)
@@ -492,6 +431,8 @@ class AnsibleModule(object):
try:
error = self.validation_result.errors[0]
+ if isinstance(error, UnsupportedError) and self._ignore_unknown_opts:
+ error = None
except IndexError:
error = None
@@ -568,7 +509,7 @@ class AnsibleModule(object):
raise AssertionError("implementation error -- version and date must not both be set")
deprecate(msg, version=version, date=date, collection_name=collection_name)
# For compatibility, we accept that neither version nor date is set,
- # and treat that the same as if version would haven been set
+ # and treat that the same as if version would not have been set
if date is not None:
self.log('[DEPRECATION WARNING] %s %s' % (msg, date))
else:
@@ -695,7 +636,7 @@ class AnsibleModule(object):
def find_mount_point(self, path):
'''
- Takes a path and returns it's mount point
+ Takes a path and returns its mount point
:param path: a string type with a filesystem path
:returns: the path to the mount point as a text type
@@ -891,7 +832,7 @@ class AnsibleModule(object):
details=to_native(e))
if mode != stat.S_IMODE(mode):
- # prevent mode from having extra info orbeing invalid long number
+ # prevent mode from having extra info or being invalid long number
path = to_text(b_path)
self.fail_json(path=path, msg="Invalid mode supplied, only permission info is allowed", details=mode)
@@ -968,7 +909,7 @@ class AnsibleModule(object):
attr_mod = attributes[0]
attributes = attributes[1:]
- if existing.get('attr_flags', '') != attributes or attr_mod == '-':
+ if attributes and (existing.get('attr_flags', '') != attributes or attr_mod == '-'):
attrcmd = self.get_bin_path('chattr')
if attrcmd:
attrcmd = [attrcmd, '%s%s' % (attr_mod, attributes), b_path]
@@ -1081,7 +1022,7 @@ class AnsibleModule(object):
if prev_mode is None:
prev_mode = stat.S_IMODE(path_stat.st_mode)
is_directory = stat.S_ISDIR(path_stat.st_mode)
- has_x_permissions = (prev_mode & EXEC_PERM_BITS) > 0
+ has_x_permissions = (prev_mode & S_IXANY) > 0
apply_X_permission = is_directory or has_x_permissions
# Get the umask, if the 'user' part is empty, the effect is as if (a) were
@@ -1279,7 +1220,7 @@ class AnsibleModule(object):
facility = getattr(syslog, self._syslog_facility, syslog.LOG_USER)
syslog.openlog(str(module), 0, facility)
syslog.syslog(syslog.LOG_INFO, msg)
- except TypeError as e:
+ except (TypeError, ValueError) as e:
self.fail_json(
msg='Failed to log to syslog (%s). To proceed anyway, '
'disable syslog logging by setting no_target_syslog '
@@ -1300,25 +1241,26 @@ class AnsibleModule(object):
log_args = dict()
module = 'ansible-%s' % self._name
- if isinstance(module, binary_type):
+ if isinstance(module, bytes):
module = module.decode('utf-8', 'replace')
# 6655 - allow for accented characters
- if not isinstance(msg, (binary_type, text_type)):
+ if not isinstance(msg, (bytes, str)):
raise TypeError("msg should be a string (got %s)" % type(msg))
# We want journal to always take text type
# syslog takes bytes on py2, text type on py3
- if isinstance(msg, binary_type):
- journal_msg = remove_values(msg.decode('utf-8', 'replace'), self.no_log_values)
+ if isinstance(msg, bytes):
+ journal_msg = msg.decode('utf-8', 'replace')
else:
# TODO: surrogateescape is a danger here on Py3
- journal_msg = remove_values(msg, self.no_log_values)
+ journal_msg = msg
- if PY3:
- syslog_msg = journal_msg
- else:
- syslog_msg = journal_msg.encode('utf-8', 'replace')
+ if self._target_log_info:
+ journal_msg = ' '.join([self._target_log_info, journal_msg])
+
+ # ensure we clean up secrets!
+ journal_msg = remove_values(journal_msg, self.no_log_values)
if has_journal:
journal_args = [("MODULE", os.path.basename(__file__))]
@@ -1349,9 +1291,9 @@ class AnsibleModule(object):
**dict(journal_args))
except IOError:
# fall back to syslog since logging to journal failed
- self._log_to_syslog(syslog_msg)
+ self._log_to_syslog(journal_msg)
else:
- self._log_to_syslog(syslog_msg)
+ self._log_to_syslog(journal_msg)
def _log_invocation(self):
''' log that ansible ran the module '''
@@ -1372,9 +1314,9 @@ class AnsibleModule(object):
log_args[param] = 'NOT_LOGGING_PARAMETER'
else:
param_val = self.params[param]
- if not isinstance(param_val, (text_type, binary_type)):
+ if not isinstance(param_val, (str, bytes)):
param_val = str(param_val)
- elif isinstance(param_val, text_type):
+ elif isinstance(param_val, str):
param_val = param_val.encode('utf-8')
log_args[param] = heuristic_log_sanitize(param_val, self.no_log_values)
@@ -1520,12 +1462,7 @@ class AnsibleModule(object):
# Add traceback if debug or high verbosity and it is missing
# NOTE: Badly named as exception, it really always has been a traceback
if 'exception' not in kwargs and sys.exc_info()[2] and (self._debug or self._verbosity >= 3):
- if PY2:
- # On Python 2 this is the last (stack frame) exception and as such may be unrelated to the failure
- kwargs['exception'] = 'WARNING: The below traceback may *not* be related to the actual failure.\n' +\
- ''.join(traceback.format_tb(sys.exc_info()[2]))
- else:
- kwargs['exception'] = ''.join(traceback.format_tb(sys.exc_info()[2]))
+ kwargs['exception'] = ''.join(traceback.format_tb(sys.exc_info()[2]))
self.do_cleanup_files()
self._return_formatted(kwargs)
@@ -1648,7 +1585,7 @@ class AnsibleModule(object):
current_attribs = current_attribs.get('attr_flags', '')
self.set_attributes_if_different(dest, current_attribs, True)
- def atomic_move(self, src, dest, unsafe_writes=False):
+ def atomic_move(self, src, dest, unsafe_writes=False, keep_dest_attrs=True):
'''atomically move src to dest, copying attributes from dest, returns true on success
it uses os.rename to ensure this as it is an atomic operation, rest of the function is
to work around limitations, corner cases and ensure selinux context is saved if possible'''
@@ -1656,24 +1593,11 @@ class AnsibleModule(object):
dest_stat = None
b_src = to_bytes(src, errors='surrogate_or_strict')
b_dest = to_bytes(dest, errors='surrogate_or_strict')
- if os.path.exists(b_dest):
+ if os.path.exists(b_dest) and keep_dest_attrs:
try:
dest_stat = os.stat(b_dest)
-
- # copy mode and ownership
- os.chmod(b_src, dest_stat.st_mode & PERM_BITS)
os.chown(b_src, dest_stat.st_uid, dest_stat.st_gid)
-
- # try to copy flags if possible
- if hasattr(os, 'chflags') and hasattr(dest_stat, 'st_flags'):
- try:
- os.chflags(b_src, dest_stat.st_flags)
- except OSError as e:
- for err in 'EOPNOTSUPP', 'ENOTSUP':
- if hasattr(errno, err) and e.errno == getattr(errno, err):
- break
- else:
- raise
+ shutil.copystat(b_dest, b_src)
except OSError as e:
if e.errno != errno.EPERM:
raise
@@ -1721,18 +1645,21 @@ class AnsibleModule(object):
os.close(tmp_dest_fd)
# leaves tmp file behind when sudo and not root
try:
- shutil.move(b_src, b_tmp_dest_name)
+ shutil.move(b_src, b_tmp_dest_name, copy_function=shutil.copy if keep_dest_attrs else shutil.copy2)
except OSError:
# cleanup will happen by 'rm' of tmpdir
# copy2 will preserve some metadata
- shutil.copy2(b_src, b_tmp_dest_name)
+ if keep_dest_attrs:
+ shutil.copy(b_src, b_tmp_dest_name)
+ else:
+ shutil.copy2(b_src, b_tmp_dest_name)
if self.selinux_enabled():
self.set_context_if_different(
b_tmp_dest_name, context, False)
try:
tmp_stat = os.stat(b_tmp_dest_name)
- if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
+ if keep_dest_attrs and dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid)
except OSError as e:
if e.errno != errno.EPERM:
@@ -1758,7 +1685,7 @@ class AnsibleModule(object):
# based on the current value of umask
umask = os.umask(0)
os.umask(umask)
- os.chmod(b_dest, DEFAULT_PERM & ~umask)
+ os.chmod(b_dest, S_IRWU_RWG_RWO & ~umask)
try:
os.chown(b_dest, os.geteuid(), os.getegid())
except OSError:
@@ -1794,13 +1721,9 @@ class AnsibleModule(object):
# create a printable version of the command for use in reporting later,
# which strips out things like passwords from the args list
to_clean_args = args
- if PY2:
- if isinstance(args, text_type):
- to_clean_args = to_bytes(args)
- else:
- if isinstance(args, binary_type):
- to_clean_args = to_text(args)
- if isinstance(args, (text_type, binary_type)):
+ if isinstance(args, bytes):
+ to_clean_args = to_text(args)
+ if isinstance(args, (str, bytes)):
to_clean_args = shlex.split(to_clean_args)
clean_args = []
@@ -1819,15 +1742,10 @@ class AnsibleModule(object):
is_passwd = True
arg = heuristic_log_sanitize(arg, self.no_log_values)
clean_args.append(arg)
- self._clean = ' '.join(shlex_quote(arg) for arg in clean_args)
+ self._clean = ' '.join(shlex.quote(arg) for arg in clean_args)
return self._clean
- def _restore_signal_handlers(self):
- # Reset SIGPIPE to SIG_DFL, otherwise in Python2.7 it gets ignored in subprocesses.
- if PY2 and sys.platform != 'win32':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None,
use_unsafe_shell=False, prompt_regex=None, environ_update=None, umask=None, encoding='utf-8', errors='surrogate_or_strict',
expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None, ignore_invalid_cwd=True, handle_exceptions=True):
@@ -1904,7 +1822,7 @@ class AnsibleModule(object):
# used by clean args later on
self._clean = None
- if not isinstance(args, (list, binary_type, text_type)):
+ if not isinstance(args, (list, bytes, str)):
msg = "Argument 'args' to run_command must be list or string"
self.fail_json(rc=257, cmd=args, msg=msg)
@@ -1913,7 +1831,7 @@ class AnsibleModule(object):
# stringify args for unsafe/direct shell usage
if isinstance(args, list):
- args = b" ".join([to_bytes(shlex_quote(x), errors='surrogate_or_strict') for x in args])
+ args = b" ".join([to_bytes(shlex.quote(x), errors='surrogate_or_strict') for x in args])
else:
args = to_bytes(args, errors='surrogate_or_strict')
@@ -1927,14 +1845,8 @@ class AnsibleModule(object):
shell = True
else:
# ensure args are a list
- if isinstance(args, (binary_type, text_type)):
- # On python2.6 and below, shlex has problems with text type
- # On python3, shlex needs a text type.
- if PY2:
- args = to_bytes(args, errors='surrogate_or_strict')
- elif PY3:
- args = to_text(args, errors='surrogateescape')
- args = shlex.split(args)
+ if isinstance(args, (bytes, str)):
+ args = shlex.split(to_text(args, errors='surrogateescape'))
# expand ``~`` in paths, and all environment vars
if expand_user_and_vars:
@@ -1944,11 +1856,8 @@ class AnsibleModule(object):
prompt_re = None
if prompt_regex:
- if isinstance(prompt_regex, text_type):
- if PY3:
- prompt_regex = to_bytes(prompt_regex, errors='surrogateescape')
- elif PY2:
- prompt_regex = to_bytes(prompt_regex, errors='surrogate_or_strict')
+ if isinstance(prompt_regex, str):
+ prompt_regex = to_bytes(prompt_regex, errors='surrogateescape')
try:
prompt_re = re.compile(prompt_regex, re.MULTILINE)
except re.error:
@@ -1987,7 +1896,6 @@ class AnsibleModule(object):
st_in = subprocess.PIPE
def preexec():
- self._restore_signal_handlers()
if umask:
os.umask(umask)
@@ -2001,10 +1909,8 @@ class AnsibleModule(object):
preexec_fn=preexec,
env=env,
)
- if PY3 and pass_fds:
+ if pass_fds:
kwargs["pass_fds"] = pass_fds
- elif PY2 and pass_fds:
- kwargs['close_fds'] = False
# make sure we're in the right working directory
if cwd:
@@ -2036,7 +1942,7 @@ class AnsibleModule(object):
if data:
if not binary_data:
data += '\n'
- if isinstance(data, text_type):
+ if isinstance(data, str):
data = to_bytes(data)
selector.register(cmd.stdout, selectors.EVENT_READ)
@@ -2155,3 +2061,52 @@ class AnsibleModule(object):
def get_module_path():
return os.path.dirname(os.path.realpath(__file__))
+
+
+def __getattr__(importable_name):
+ """Inject import-time deprecation warnings."""
+ if importable_name == 'get_exception':
+ from ansible.module_utils.pycompat24 import get_exception
+ importable = get_exception
+ elif importable_name in {'literal_eval', '_literal_eval'}:
+ from ast import literal_eval
+ importable = literal_eval
+ elif importable_name == 'datetime':
+ import datetime
+ importable = datetime
+ elif importable_name == 'signal':
+ import signal
+ importable = signal
+ elif importable_name == 'types':
+ import types
+ importable = types
+ elif importable_name == 'chain':
+ from itertools import chain
+ importable = chain
+ elif importable_name == 'repeat':
+ from itertools import repeat
+ importable = repeat
+ elif importable_name in {
+ 'PY2', 'PY3', 'b', 'binary_type', 'integer_types',
+ 'iteritems', 'string_types', 'test_type'
+ }:
+ import importlib
+ importable = getattr(
+ importlib.import_module('ansible.module_utils.six'),
+ importable_name
+ )
+ elif importable_name == 'map':
+ importable = map
+ elif importable_name == 'shlex_quote':
+ importable = shlex.quote
+ else:
+ raise AttributeError(
+ f'cannot import name {importable_name !r} '
+ f"from '{__name__}' ({__file__ !s})"
+ )
+
+ deprecate(
+ msg=f"Importing '{importable_name}' from '{__name__}' is deprecated.",
+ version="2.21",
+ )
+ return importable
diff --git a/lib/ansible/module_utils/common/_collections_compat.py b/lib/ansible/module_utils/common/_collections_compat.py
index f0f8f0d..25f7889 100644
--- a/lib/ansible/module_utils/common/_collections_compat.py
+++ b/lib/ansible/module_utils/common/_collections_compat.py
@@ -6,8 +6,7 @@ Use `ansible.module_utils.six.moves.collections_abc` instead, which has been ava
This module exists only for backwards compatibility.
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
# Although this was originally intended for internal use only, it has wide adoption in collections.
# This is due in part to sanity tests previously recommending its use over `collections` imports.
diff --git a/lib/ansible/module_utils/common/_json_compat.py b/lib/ansible/module_utils/common/_json_compat.py
deleted file mode 100644
index 787af0f..0000000
--- a/lib/ansible/module_utils/common/_json_compat.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2019 Ansible Project
-# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-import types
-import json
-
-# Detect the python-json library which is incompatible
-try:
- if not isinstance(json.loads, types.FunctionType) or not isinstance(json.dumps, types.FunctionType):
- raise ImportError('json.loads or json.dumps were not found in the imported json library.')
-except AttributeError:
- raise ImportError('python-json was detected, which is incompatible.')
diff --git a/lib/ansible/module_utils/common/_utils.py b/lib/ansible/module_utils/common/_utils.py
index 66df316..8323e7c 100644
--- a/lib/ansible/module_utils/common/_utils.py
+++ b/lib/ansible/module_utils/common/_utils.py
@@ -1,14 +1,12 @@
# Copyright (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
"""
Modules in _utils are waiting to find a better home. If you need to use them, be prepared for them
to move to a different location in the future.
"""
+from __future__ import annotations
def get_all_subclasses(cls):
diff --git a/lib/ansible/module_utils/common/arg_spec.py b/lib/ansible/module_utils/common/arg_spec.py
index d9f716e..37019e7 100644
--- a/lib/ansible/module_utils/common/arg_spec.py
+++ b/lib/ansible/module_utils/common/arg_spec.py
@@ -2,8 +2,7 @@
# Copyright (c) 2021 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
from copy import deepcopy
diff --git a/lib/ansible/module_utils/common/collections.py b/lib/ansible/module_utils/common/collections.py
index 06f08a8..e4cb9ec 100644
--- a/lib/ansible/module_utils/common/collections.py
+++ b/lib/ansible/module_utils/common/collections.py
@@ -3,8 +3,7 @@
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
"""Collection of low-level utility functions."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.six import binary_type, text_type
diff --git a/lib/ansible/module_utils/common/dict_transformations.py b/lib/ansible/module_utils/common/dict_transformations.py
index 9ee7878..9c59d4a 100644
--- a/lib/ansible/module_utils/common/dict_transformations.py
+++ b/lib/ansible/module_utils/common/dict_transformations.py
@@ -3,8 +3,7 @@
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/common/file.py b/lib/ansible/module_utils/common/file.py
index 72b0d2c..b62e4c6 100644
--- a/lib/ansible/module_utils/common/file.py
+++ b/lib/ansible/module_utils/common/file.py
@@ -1,8 +1,7 @@
# Copyright (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import stat
@@ -45,9 +44,15 @@ USERS_RE = re.compile(r'[^ugo]')
PERMS_RE = re.compile(r'[^rwxXstugo]')
-_PERM_BITS = 0o7777 # file mode permission bits
-_EXEC_PERM_BITS = 0o0111 # execute permission bits
-_DEFAULT_PERM = 0o0666 # default file permission bits
+S_IRANY = 0o0444 # read by user, group, others
+S_IWANY = 0o0222 # write by user, group, others
+S_IXANY = 0o0111 # execute by user, group, others
+S_IRWU_RWG_RWO = S_IRANY | S_IWANY # read, write by user, group, others
+S_IRWU_RG_RO = S_IRANY | stat.S_IWUSR # read by user, group, others and write only by user
+S_IRWXU_RXG_RXO = S_IRANY | S_IXANY | stat.S_IWUSR # read, execute by user, group, others and write only by user
+_PERM_BITS = 0o7777 # file mode permission bits
+_EXEC_PERM_BITS = S_IXANY # execute permission bits
+_DEFAULT_PERM = S_IRWU_RWG_RWO # default file permission bits
def is_executable(path):
diff --git a/lib/ansible/module_utils/common/json.py b/lib/ansible/module_utils/common/json.py
index 639e7b9..537c003 100644
--- a/lib/ansible/module_utils/common/json.py
+++ b/lib/ansible/module_utils/common/json.py
@@ -2,9 +2,7 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import json
diff --git a/lib/ansible/module_utils/common/locale.py b/lib/ansible/module_utils/common/locale.py
index 08216f5..57b27a2 100644
--- a/lib/ansible/module_utils/common/locale.py
+++ b/lib/ansible/module_utils/common/locale.py
@@ -1,8 +1,7 @@
# Copyright (c), Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.common.text.converters import to_native
diff --git a/lib/ansible/module_utils/common/network.py b/lib/ansible/module_utils/common/network.py
index c3874f8..a85fc1c 100644
--- a/lib/ansible/module_utils/common/network.py
+++ b/lib/ansible/module_utils/common/network.py
@@ -3,8 +3,7 @@
# General networking tools that may be used by all modules
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
from struct import pack
@@ -62,7 +61,7 @@ def to_masklen(val):
def to_subnet(addr, mask, dotted_notation=False):
- """ coverts an addr / mask pair to a subnet in cidr notation """
+ """ converts an addr / mask pair to a subnet in cidr notation """
try:
if not is_masklen(mask):
raise ValueError
diff --git a/lib/ansible/module_utils/common/parameters.py b/lib/ansible/module_utils/common/parameters.py
index 386eb87..b9f5be4 100644
--- a/lib/ansible/module_utils/common/parameters.py
+++ b/lib/ansible/module_utils/common/parameters.py
@@ -2,8 +2,7 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import datetime
import os
@@ -83,14 +82,17 @@ _ADDITIONAL_CHECKS = (
# if adding boolean attribute, also add to PASS_BOOL
# some of this dupes defaults from controller config
+# keep in sync with copy in lib/ansible/module_utils/csharp/Ansible.Basic.cs
PASS_VARS = {
'check_mode': ('check_mode', False),
'debug': ('_debug', False),
'diff': ('_diff', False),
'keep_remote_files': ('_keep_remote_files', False),
+ 'ignore_unknown_opts': ('_ignore_unknown_opts', False),
'module_name': ('_name', None),
'no_log': ('no_log', False),
'remote_tmp': ('_remote_tmp', None),
+ 'target_log_info': ('_target_log_info', None),
'selinux_special_fs': ('_selinux_special_fs', ['fuse', 'nfs', 'vboxsf', 'ramfs', '9p', 'vfat']),
'shell_executable': ('_shell', '/bin/sh'),
'socket': ('_socket_path', None),
@@ -101,7 +103,7 @@ PASS_VARS = {
'version': ('ansible_version', '0.0'),
}
-PASS_BOOLS = ('check_mode', 'debug', 'diff', 'keep_remote_files', 'no_log')
+PASS_BOOLS = ('check_mode', 'debug', 'diff', 'keep_remote_files', 'ignore_unknown_opts', 'no_log')
DEFAULT_TYPE_VALIDATORS = {
'str': check_type_str,
@@ -345,7 +347,7 @@ def _list_no_log_values(argument_spec, params):
sub_param = check_type_dict(sub_param)
if not isinstance(sub_param, Mapping):
- raise TypeError("Value '{1}' in the sub parameter field '{0}' must by a {2}, "
+ raise TypeError("Value '{1}' in the sub parameter field '{0}' must be a {2}, "
"not '{1.__class__.__name__}'".format(arg_name, sub_param, wanted_type))
no_log_values.update(_list_no_log_values(sub_argument_spec, sub_param))
@@ -363,12 +365,10 @@ def _return_datastructure_name(obj):
return
elif isinstance(obj, Mapping):
for element in obj.items():
- for subelement in _return_datastructure_name(element[1]):
- yield subelement
+ yield from _return_datastructure_name(element[1])
elif is_iterable(obj):
for element in obj:
- for subelement in _return_datastructure_name(element):
- yield subelement
+ yield from _return_datastructure_name(element)
elif obj is None or isinstance(obj, bool):
# This must come before int because bools are also ints
return
@@ -663,7 +663,7 @@ def _validate_argument_values(argument_spec, parameters, options_context=None, e
diff_list = [item for item in parameters[param] if item not in choices]
if diff_list:
choices_str = ", ".join([to_native(c) for c in choices])
- diff_str = ", ".join(diff_list)
+ diff_str = ", ".join([to_native(c) for c in diff_list])
msg = "value of %s must be one or more of: %s. Got no match for: %s" % (param, choices_str, diff_str)
if options_context:
msg = "{0} found in {1}".format(msg, " -> ".join(options_context))
diff --git a/lib/ansible/module_utils/common/process.py b/lib/ansible/module_utils/common/process.py
index 97761a4..8e62c5f 100644
--- a/lib/ansible/module_utils/common/process.py
+++ b/lib/ansible/module_utils/common/process.py
@@ -1,25 +1,32 @@
# Copyright (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
from ansible.module_utils.common.file import is_executable
+from ansible.module_utils.common.warnings import deprecate
def get_bin_path(arg, opt_dirs=None, required=None):
'''
- Find system executable in PATH. Raises ValueError if executable is not found.
+ Find system executable in PATH. Raises ValueError if the executable is not found.
Optional arguments:
- - required: [Deprecated] Prior to 2.10, if executable is not found and required is true it raises an Exception.
- In 2.10 and later, an Exception is always raised. This parameter will be removed in 2.14.
+ - required: [Deprecated] Before 2.10, if executable is not found and required is true it raises an Exception.
+ In 2.10 and later, an Exception is always raised. This parameter will be removed in 2.21.
- opt_dirs: optional list of directories to search in addition to PATH
In addition to PATH and opt_dirs, this function also looks through /sbin, /usr/sbin and /usr/local/sbin. A lot of
modules, especially for gathering facts, depend on this behaviour.
If found return full path, otherwise raise ValueError.
'''
+ if required is not None:
+ deprecate(
+ msg="The `required` parameter in `get_bin_path` API is deprecated.",
+ version="2.21",
+ collection_name="ansible.builtin",
+ )
+
opt_dirs = [] if opt_dirs is None else opt_dirs
sbin_paths = ['/sbin', '/usr/sbin', '/usr/local/sbin']
diff --git a/lib/ansible/module_utils/common/respawn.py b/lib/ansible/module_utils/common/respawn.py
index 3e209ca..0f57c15 100644
--- a/lib/ansible/module_utils/common/respawn.py
+++ b/lib/ansible/module_utils/common/respawn.py
@@ -1,8 +1,7 @@
# Copyright: (c) 2021, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import subprocess
@@ -20,7 +19,7 @@ def respawn_module(interpreter_path):
Respawn the currently-running Ansible Python module under the specified Python interpreter.
Ansible modules that require libraries that are typically available only under well-known interpreters
- (eg, ``yum``, ``apt``, ``dnf``) can use bespoke logic to determine the libraries they need are not
+ (eg, ``apt``, ``dnf``) can use bespoke logic to determine the libraries they need are not
available, then call `respawn_module` to re-execute the current module under a different interpreter
and exit the current process when the new subprocess has completed. The respawned process inherits only
stdout/stderr from the current process.
diff --git a/lib/ansible/module_utils/common/sys_info.py b/lib/ansible/module_utils/common/sys_info.py
index 206b36c..6ca4510 100644
--- a/lib/ansible/module_utils/common/sys_info.py
+++ b/lib/ansible/module_utils/common/sys_info.py
@@ -2,8 +2,7 @@
# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com> 2016
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import platform
diff --git a/lib/ansible/module_utils/common/text/converters.py b/lib/ansible/module_utils/common/text/converters.py
index 5b41315..abef32d 100644
--- a/lib/ansible/module_utils/common/text/converters.py
+++ b/lib/ansible/module_utils/common/text/converters.py
@@ -3,8 +3,7 @@
# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import codecs
import datetime
diff --git a/lib/ansible/module_utils/common/text/formatters.py b/lib/ansible/module_utils/common/text/formatters.py
index 0c3d495..3096abe 100644
--- a/lib/ansible/module_utils/common/text/formatters.py
+++ b/lib/ansible/module_utils/common/text/formatters.py
@@ -2,8 +2,7 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/common/validation.py b/lib/ansible/module_utils/common/validation.py
index cc54789..69721e4 100644
--- a/lib/ansible/module_utils/common/validation.py
+++ b/lib/ansible/module_utils/common/validation.py
@@ -2,15 +2,14 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
+import json
import os
import re
from ast import literal_eval
from ansible.module_utils.common.text.converters import to_native
-from ansible.module_utils.common._json_compat import json
from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.common.text.converters import jsonify
from ansible.module_utils.common.text.formatters import human_to_bytes
@@ -543,7 +542,7 @@ def check_type_raw(value):
def check_type_bytes(value):
"""Convert a human-readable string value to bytes
- Raises :class:`TypeError` if unable to covert the value
+ Raises :class:`TypeError` if unable to convert the value
"""
try:
return human_to_bytes(value)
@@ -556,7 +555,7 @@ def check_type_bits(value):
Example: ``check_type_bits('1Mb')`` returns integer 1048576.
- Raises :class:`TypeError` if unable to covert the value.
+ Raises :class:`TypeError` if unable to convert the value.
"""
try:
return human_to_bytes(value, isbits=True)
@@ -568,7 +567,7 @@ def check_type_jsonarg(value):
"""Return a jsonified string. Sometimes the controller turns a json string
into a dict/list so transform it back into json here
- Raises :class:`TypeError` if unable to covert the value
+ Raises :class:`TypeError` if unable to convert the value
"""
if isinstance(value, (text_type, binary_type)):
diff --git a/lib/ansible/module_utils/common/warnings.py b/lib/ansible/module_utils/common/warnings.py
index 9423e6a..14fe516 100644
--- a/lib/ansible/module_utils/common/warnings.py
+++ b/lib/ansible/module_utils/common/warnings.py
@@ -2,8 +2,7 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.six import string_types
diff --git a/lib/ansible/module_utils/common/yaml.py b/lib/ansible/module_utils/common/yaml.py
index b4d766b..2e1ee52 100644
--- a/lib/ansible/module_utils/common/yaml.py
+++ b/lib/ansible/module_utils/common/yaml.py
@@ -6,8 +6,7 @@ This file provides ease of use shortcuts for loading and dumping YAML,
preferring the YAML compiled C extensions to reduce duplicated code.
"""
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from functools import partial as _partial
diff --git a/lib/ansible/module_utils/compat/_selectors2.py b/lib/ansible/module_utils/compat/_selectors2.py
deleted file mode 100644
index 4a4fcc3..0000000
--- a/lib/ansible/module_utils/compat/_selectors2.py
+++ /dev/null
@@ -1,655 +0,0 @@
-# This file is from the selectors2.py package. It backports the PSF Licensed
-# selectors module from the Python-3.5 stdlib to older versions of Python.
-# The author, Seth Michael Larson, dual licenses his modifications under the
-# PSF License and MIT License:
-# https://github.com/SethMichaelLarson/selectors2#license
-#
-# Copyright (c) 2016 Seth Michael Larson
-#
-# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0)
-# MIT License (see licenses/MIT-license.txt or https://opensource.org/licenses/MIT)
-#
-
-
-# Backport of selectors.py from Python 3.5+ to support Python < 3.4
-# Also has the behavior specified in PEP 475 which is to retry syscalls
-# in the case of an EINTR error. This module is required because selectors34
-# does not follow this behavior and instead returns that no file descriptor
-# events have occurred rather than retry the syscall. The decision to drop
-# support for select.devpoll is made to maintain 100% test coverage.
-
-import errno
-import math
-import select
-import socket
-import sys
-import time
-from collections import namedtuple
-from ansible.module_utils.six.moves.collections_abc import Mapping
-
-try:
- monotonic = time.monotonic
-except (AttributeError, ImportError): # Python 3.3<
- monotonic = time.time
-
-__author__ = 'Seth Michael Larson'
-__email__ = 'sethmichaellarson@protonmail.com'
-__version__ = '1.1.1'
-__license__ = 'MIT'
-
-__all__ = [
- 'EVENT_READ',
- 'EVENT_WRITE',
- 'SelectorError',
- 'SelectorKey',
- 'DefaultSelector'
-]
-
-EVENT_READ = (1 << 0)
-EVENT_WRITE = (1 << 1)
-
-HAS_SELECT = True # Variable that shows whether the platform has a selector.
-_SYSCALL_SENTINEL = object() # Sentinel in case a system call returns None.
-
-
-class SelectorError(Exception):
- def __init__(self, errcode):
- super(SelectorError, self).__init__()
- self.errno = errcode
-
- def __repr__(self):
- return "<SelectorError errno={0}>".format(self.errno)
-
- def __str__(self):
- return self.__repr__()
-
-
-def _fileobj_to_fd(fileobj):
- """ Return a file descriptor from a file object. If
- given an integer will simply return that integer back. """
- if isinstance(fileobj, int):
- fd = fileobj
- else:
- try:
- fd = int(fileobj.fileno())
- except (AttributeError, TypeError, ValueError):
- raise ValueError("Invalid file object: {0!r}".format(fileobj))
- if fd < 0:
- raise ValueError("Invalid file descriptor: {0}".format(fd))
- return fd
-
-
-# Python 3.5 uses a more direct route to wrap system calls to increase speed.
-if sys.version_info >= (3, 5):
- def _syscall_wrapper(func, dummy, *args, **kwargs):
- """ This is the short-circuit version of the below logic
- because in Python 3.5+ all selectors restart system calls. """
- try:
- return func(*args, **kwargs)
- except (OSError, IOError, select.error) as e:
- errcode = None
- if hasattr(e, "errno"):
- errcode = e.errno
- elif hasattr(e, "args"):
- errcode = e.args[0]
- raise SelectorError(errcode)
-else:
- def _syscall_wrapper(func, recalc_timeout, *args, **kwargs):
- """ Wrapper function for syscalls that could fail due to EINTR.
- All functions should be retried if there is time left in the timeout
- in accordance with PEP 475. """
- timeout = kwargs.get("timeout", None)
- if timeout is None:
- expires = None
- recalc_timeout = False
- else:
- timeout = float(timeout)
- if timeout < 0.0: # Timeout less than 0 treated as no timeout.
- expires = None
- else:
- expires = monotonic() + timeout
-
- args = list(args)
- if recalc_timeout and "timeout" not in kwargs:
- raise ValueError(
- "Timeout must be in args or kwargs to be recalculated")
-
- result = _SYSCALL_SENTINEL
- while result is _SYSCALL_SENTINEL:
- try:
- result = func(*args, **kwargs)
- # OSError is thrown by select.select
- # IOError is thrown by select.epoll.poll
- # select.error is thrown by select.poll.poll
- # Aren't we thankful for Python 3.x rework for exceptions?
- except (OSError, IOError, select.error) as e:
- # select.error wasn't a subclass of OSError in the past.
- errcode = None
- if hasattr(e, "errno"):
- errcode = e.errno
- elif hasattr(e, "args"):
- errcode = e.args[0]
-
- # Also test for the Windows equivalent of EINTR.
- is_interrupt = (errcode == errno.EINTR or (hasattr(errno, "WSAEINTR") and
- errcode == errno.WSAEINTR))
-
- if is_interrupt:
- if expires is not None:
- current_time = monotonic()
- if current_time > expires:
- raise OSError(errno.ETIMEDOUT)
- if recalc_timeout:
- if "timeout" in kwargs:
- kwargs["timeout"] = expires - current_time
- continue
- if errcode:
- raise SelectorError(errcode)
- else:
- raise
- return result
-
-
-SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
-
-
-class _SelectorMapping(Mapping):
- """ Mapping of file objects to selector keys """
-
- def __init__(self, selector):
- self._selector = selector
-
- def __len__(self):
- return len(self._selector._fd_to_key)
-
- def __getitem__(self, fileobj):
- try:
- fd = self._selector._fileobj_lookup(fileobj)
- return self._selector._fd_to_key[fd]
- except KeyError:
- raise KeyError("{0!r} is not registered.".format(fileobj))
-
- def __iter__(self):
- return iter(self._selector._fd_to_key)
-
-
-class BaseSelector(object):
- """ Abstract Selector class
-
- A selector supports registering file objects to be monitored
- for specific I/O events.
-
- A file object is a file descriptor or any object with a
- `fileno()` method. An arbitrary object can be attached to the
- file object which can be used for example to store context info,
- a callback, etc.
-
- A selector can use various implementations (select(), poll(), epoll(),
- and kqueue()) depending on the platform. The 'DefaultSelector' class uses
- the most efficient implementation for the current platform.
- """
- def __init__(self):
- # Maps file descriptors to keys.
- self._fd_to_key = {}
-
- # Read-only mapping returned by get_map()
- self._map = _SelectorMapping(self)
-
- def _fileobj_lookup(self, fileobj):
- """ Return a file descriptor from a file object.
- This wraps _fileobj_to_fd() to do an exhaustive
- search in case the object is invalid but we still
- have it in our map. Used by unregister() so we can
- unregister an object that was previously registered
- even if it is closed. It is also used by _SelectorMapping
- """
- try:
- return _fileobj_to_fd(fileobj)
- except ValueError:
-
- # Search through all our mapped keys.
- for key in self._fd_to_key.values():
- if key.fileobj is fileobj:
- return key.fd
-
- # Raise ValueError after all.
- raise
-
- def register(self, fileobj, events, data=None):
- """ Register a file object for a set of events to monitor. """
- if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
- raise ValueError("Invalid events: {0!r}".format(events))
-
- key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
-
- if key.fd in self._fd_to_key:
- raise KeyError("{0!r} (FD {1}) is already registered"
- .format(fileobj, key.fd))
-
- self._fd_to_key[key.fd] = key
- return key
-
- def unregister(self, fileobj):
- """ Unregister a file object from being monitored. """
- try:
- key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))
- except KeyError:
- raise KeyError("{0!r} is not registered".format(fileobj))
-
- # Getting the fileno of a closed socket on Windows errors with EBADF.
- except socket.error as err:
- if err.errno != errno.EBADF:
- raise
- else:
- for key in self._fd_to_key.values():
- if key.fileobj is fileobj:
- self._fd_to_key.pop(key.fd)
- break
- else:
- raise KeyError("{0!r} is not registered".format(fileobj))
- return key
-
- def modify(self, fileobj, events, data=None):
- """ Change a registered file object monitored events and data. """
- # NOTE: Some subclasses optimize this operation even further.
- try:
- key = self._fd_to_key[self._fileobj_lookup(fileobj)]
- except KeyError:
- raise KeyError("{0!r} is not registered".format(fileobj))
-
- if events != key.events:
- self.unregister(fileobj)
- key = self.register(fileobj, events, data)
-
- elif data != key.data:
- # Use a shortcut to update the data.
- key = key._replace(data=data)
- self._fd_to_key[key.fd] = key
-
- return key
-
- def select(self, timeout=None):
- """ Perform the actual selection until some monitored file objects
- are ready or the timeout expires. """
- raise NotImplementedError()
-
- def close(self):
- """ Close the selector. This must be called to ensure that all
- underlying resources are freed. """
- self._fd_to_key.clear()
- self._map = None
-
- def get_key(self, fileobj):
- """ Return the key associated with a registered file object. """
- mapping = self.get_map()
- if mapping is None:
- raise RuntimeError("Selector is closed")
- try:
- return mapping[fileobj]
- except KeyError:
- raise KeyError("{0!r} is not registered".format(fileobj))
-
- def get_map(self):
- """ Return a mapping of file objects to selector keys """
- return self._map
-
- def _key_from_fd(self, fd):
- """ Return the key associated to a given file descriptor
- Return None if it is not found. """
- try:
- return self._fd_to_key[fd]
- except KeyError:
- return None
-
- def __enter__(self):
- return self
-
- def __exit__(self, *args):
- self.close()
-
-
-# Almost all platforms have select.select()
-if hasattr(select, "select"):
- class SelectSelector(BaseSelector):
- """ Select-based selector. """
- def __init__(self):
- super(SelectSelector, self).__init__()
- self._readers = set()
- self._writers = set()
-
- def register(self, fileobj, events, data=None):
- key = super(SelectSelector, self).register(fileobj, events, data)
- if events & EVENT_READ:
- self._readers.add(key.fd)
- if events & EVENT_WRITE:
- self._writers.add(key.fd)
- return key
-
- def unregister(self, fileobj):
- key = super(SelectSelector, self).unregister(fileobj)
- self._readers.discard(key.fd)
- self._writers.discard(key.fd)
- return key
-
- def _select(self, r, w, timeout=None):
- """ Wrapper for select.select because timeout is a positional arg """
- return select.select(r, w, [], timeout)
-
- def select(self, timeout=None):
- # Selecting on empty lists on Windows errors out.
- if not len(self._readers) and not len(self._writers):
- return []
-
- timeout = None if timeout is None else max(timeout, 0.0)
- ready = []
- r, w, dummy = _syscall_wrapper(self._select, True, self._readers,
- self._writers, timeout=timeout)
- r = set(r)
- w = set(w)
- for fd in r | w:
- events = 0
- if fd in r:
- events |= EVENT_READ
- if fd in w:
- events |= EVENT_WRITE
-
- key = self._key_from_fd(fd)
- if key:
- ready.append((key, events & key.events))
- return ready
-
- __all__.append('SelectSelector')
-
-
-if hasattr(select, "poll"):
- class PollSelector(BaseSelector):
- """ Poll-based selector """
- def __init__(self):
- super(PollSelector, self).__init__()
- self._poll = select.poll()
-
- def register(self, fileobj, events, data=None):
- key = super(PollSelector, self).register(fileobj, events, data)
- event_mask = 0
- if events & EVENT_READ:
- event_mask |= select.POLLIN
- if events & EVENT_WRITE:
- event_mask |= select.POLLOUT
- self._poll.register(key.fd, event_mask)
- return key
-
- def unregister(self, fileobj):
- key = super(PollSelector, self).unregister(fileobj)
- self._poll.unregister(key.fd)
- return key
-
- def _wrap_poll(self, timeout=None):
- """ Wrapper function for select.poll.poll() so that
- _syscall_wrapper can work with only seconds. """
- if timeout is not None:
- if timeout <= 0:
- timeout = 0
- else:
- # select.poll.poll() has a resolution of 1 millisecond,
- # round away from zero to wait *at least* timeout seconds.
- timeout = math.ceil(timeout * 1e3)
-
- result = self._poll.poll(timeout)
- return result
-
- def select(self, timeout=None):
- ready = []
- fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout)
- for fd, event_mask in fd_events:
- events = 0
- if event_mask & ~select.POLLIN:
- events |= EVENT_WRITE
- if event_mask & ~select.POLLOUT:
- events |= EVENT_READ
-
- key = self._key_from_fd(fd)
- if key:
- ready.append((key, events & key.events))
-
- return ready
-
- __all__.append('PollSelector')
-
-if hasattr(select, "epoll"):
- class EpollSelector(BaseSelector):
- """ Epoll-based selector """
- def __init__(self):
- super(EpollSelector, self).__init__()
- self._epoll = select.epoll()
-
- def fileno(self):
- return self._epoll.fileno()
-
- def register(self, fileobj, events, data=None):
- key = super(EpollSelector, self).register(fileobj, events, data)
- events_mask = 0
- if events & EVENT_READ:
- events_mask |= select.EPOLLIN
- if events & EVENT_WRITE:
- events_mask |= select.EPOLLOUT
- _syscall_wrapper(self._epoll.register, False, key.fd, events_mask)
- return key
-
- def unregister(self, fileobj):
- key = super(EpollSelector, self).unregister(fileobj)
- try:
- _syscall_wrapper(self._epoll.unregister, False, key.fd)
- except SelectorError:
- # This can occur when the fd was closed since registry.
- pass
- return key
-
- def select(self, timeout=None):
- if timeout is not None:
- if timeout <= 0:
- timeout = 0.0
- else:
- # select.epoll.poll() has a resolution of 1 millisecond
- # but luckily takes seconds so we don't need a wrapper
- # like PollSelector. Just for better rounding.
- timeout = math.ceil(timeout * 1e3) * 1e-3
- timeout = float(timeout)
- else:
- timeout = -1.0 # epoll.poll() must have a float.
-
- # We always want at least 1 to ensure that select can be called
- # with no file descriptors registered. Otherwise will fail.
- max_events = max(len(self._fd_to_key), 1)
-
- ready = []
- fd_events = _syscall_wrapper(self._epoll.poll, True,
- timeout=timeout,
- maxevents=max_events)
- for fd, event_mask in fd_events:
- events = 0
- if event_mask & ~select.EPOLLIN:
- events |= EVENT_WRITE
- if event_mask & ~select.EPOLLOUT:
- events |= EVENT_READ
-
- key = self._key_from_fd(fd)
- if key:
- ready.append((key, events & key.events))
- return ready
-
- def close(self):
- self._epoll.close()
- super(EpollSelector, self).close()
-
- __all__.append('EpollSelector')
-
-
-if hasattr(select, "devpoll"):
- class DevpollSelector(BaseSelector):
- """Solaris /dev/poll selector."""
-
- def __init__(self):
- super(DevpollSelector, self).__init__()
- self._devpoll = select.devpoll()
-
- def fileno(self):
- return self._devpoll.fileno()
-
- def register(self, fileobj, events, data=None):
- key = super(DevpollSelector, self).register(fileobj, events, data)
- poll_events = 0
- if events & EVENT_READ:
- poll_events |= select.POLLIN
- if events & EVENT_WRITE:
- poll_events |= select.POLLOUT
- self._devpoll.register(key.fd, poll_events)
- return key
-
- def unregister(self, fileobj):
- key = super(DevpollSelector, self).unregister(fileobj)
- self._devpoll.unregister(key.fd)
- return key
-
- def _wrap_poll(self, timeout=None):
- """ Wrapper function for select.poll.poll() so that
- _syscall_wrapper can work with only seconds. """
- if timeout is not None:
- if timeout <= 0:
- timeout = 0
- else:
- # select.devpoll.poll() has a resolution of 1 millisecond,
- # round away from zero to wait *at least* timeout seconds.
- timeout = math.ceil(timeout * 1e3)
-
- result = self._devpoll.poll(timeout)
- return result
-
- def select(self, timeout=None):
- ready = []
- fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout)
- for fd, event_mask in fd_events:
- events = 0
- if event_mask & ~select.POLLIN:
- events |= EVENT_WRITE
- if event_mask & ~select.POLLOUT:
- events |= EVENT_READ
-
- key = self._key_from_fd(fd)
- if key:
- ready.append((key, events & key.events))
-
- return ready
-
- def close(self):
- self._devpoll.close()
- super(DevpollSelector, self).close()
-
- __all__.append('DevpollSelector')
-
-
-if hasattr(select, "kqueue"):
- class KqueueSelector(BaseSelector):
- """ Kqueue / Kevent-based selector """
- def __init__(self):
- super(KqueueSelector, self).__init__()
- self._kqueue = select.kqueue()
-
- def fileno(self):
- return self._kqueue.fileno()
-
- def register(self, fileobj, events, data=None):
- key = super(KqueueSelector, self).register(fileobj, events, data)
- if events & EVENT_READ:
- kevent = select.kevent(key.fd,
- select.KQ_FILTER_READ,
- select.KQ_EV_ADD)
-
- _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0)
-
- if events & EVENT_WRITE:
- kevent = select.kevent(key.fd,
- select.KQ_FILTER_WRITE,
- select.KQ_EV_ADD)
-
- _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0)
-
- return key
-
- def unregister(self, fileobj):
- key = super(KqueueSelector, self).unregister(fileobj)
- if key.events & EVENT_READ:
- kevent = select.kevent(key.fd,
- select.KQ_FILTER_READ,
- select.KQ_EV_DELETE)
- try:
- _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0)
- except SelectorError:
- pass
- if key.events & EVENT_WRITE:
- kevent = select.kevent(key.fd,
- select.KQ_FILTER_WRITE,
- select.KQ_EV_DELETE)
- try:
- _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0)
- except SelectorError:
- pass
-
- return key
-
- def select(self, timeout=None):
- if timeout is not None:
- timeout = max(timeout, 0)
-
- max_events = len(self._fd_to_key) * 2
- ready_fds = {}
-
- kevent_list = _syscall_wrapper(self._wrap_control, True,
- None, max_events, timeout=timeout)
-
- for kevent in kevent_list:
- fd = kevent.ident
- event_mask = kevent.filter
- events = 0
- if event_mask == select.KQ_FILTER_READ:
- events |= EVENT_READ
- if event_mask == select.KQ_FILTER_WRITE:
- events |= EVENT_WRITE
-
- key = self._key_from_fd(fd)
- if key:
- if key.fd not in ready_fds:
- ready_fds[key.fd] = (key, events & key.events)
- else:
- old_events = ready_fds[key.fd][1]
- ready_fds[key.fd] = (key, (events | old_events) & key.events)
-
- return list(ready_fds.values())
-
- def close(self):
- self._kqueue.close()
- super(KqueueSelector, self).close()
-
- def _wrap_control(self, changelist, max_events, timeout):
- return self._kqueue.control(changelist, max_events, timeout)
-
- __all__.append('KqueueSelector')
-
-
-# Choose the best implementation, roughly:
-# kqueue == epoll == devpoll > poll > select.
-# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
-if 'KqueueSelector' in globals(): # Platform-specific: Mac OS and BSD
- DefaultSelector = KqueueSelector
-elif 'DevpollSelector' in globals():
- DefaultSelector = DevpollSelector
-elif 'EpollSelector' in globals(): # Platform-specific: Linux
- DefaultSelector = EpollSelector
-elif 'PollSelector' in globals(): # Platform-specific: Linux
- DefaultSelector = PollSelector
-elif 'SelectSelector' in globals(): # Platform-specific: Windows
- DefaultSelector = SelectSelector
-else: # Platform-specific: AppEngine
- def no_selector(dummy):
- raise ValueError("Platform does not have a selector")
- DefaultSelector = no_selector
- HAS_SELECT = False
diff --git a/lib/ansible/module_utils/compat/datetime.py b/lib/ansible/module_utils/compat/datetime.py
index 30edaed..d3cdc0d 100644
--- a/lib/ansible/module_utils/compat/datetime.py
+++ b/lib/ansible/module_utils/compat/datetime.py
@@ -1,9 +1,7 @@
# Copyright (c) 2023 Ansible
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.six import PY3
diff --git a/lib/ansible/module_utils/compat/importlib.py b/lib/ansible/module_utils/compat/importlib.py
index a3dca6b..4074f37 100644
--- a/lib/ansible/module_utils/compat/importlib.py
+++ b/lib/ansible/module_utils/compat/importlib.py
@@ -1,18 +1,26 @@
# Copyright (c) 2020 Matt Martz <matt@sivel.net>
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
-import sys
+from ansible.module_utils.common.warnings import deprecate
-try:
- from importlib import import_module # pylint: disable=unused-import
-except ImportError:
- # importlib.import_module returns the tail
- # whereas __import__ returns the head
- # compat to work like importlib.import_module
- def import_module(name): # type: ignore[misc]
- __import__(name)
- return sys.modules[name]
+
+def __getattr__(importable_name):
+ """Inject import-time deprecation warnings.
+
+ Specifically, for ``import_module()``.
+ """
+ if importable_name == 'import_module':
+ deprecate(
+ msg=f'The `ansible.module_utils.compat.importlib.'
+ f'{importable_name}` function is deprecated.',
+ version='2.19',
+ )
+ from importlib import import_module
+ return import_module
+
+ raise AttributeError(
+ f'cannot import name {importable_name !r} '
+ f'has no attribute ({__file__ !s})',
+ )
diff --git a/lib/ansible/module_utils/compat/paramiko.py b/lib/ansible/module_utils/compat/paramiko.py
index 095dfa5..8c84261 100644
--- a/lib/ansible/module_utils/compat/paramiko.py
+++ b/lib/ansible/module_utils/compat/paramiko.py
@@ -2,8 +2,7 @@
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
import types # pylint: disable=unused-import
import warnings
diff --git a/lib/ansible/module_utils/compat/selectors.py b/lib/ansible/module_utils/compat/selectors.py
index 0c4adc9..81082f3 100644
--- a/lib/ansible/module_utils/compat/selectors.py
+++ b/lib/ansible/module_utils/compat/selectors.py
@@ -15,42 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-'''
-Compat selectors library. Python-3.5 has this builtin. The selectors2
-package exists on pypi to backport the functionality as far as python-2.6.
-'''
-# The following makes it easier for us to script updates of the bundled code
-_BUNDLED_METADATA = {"pypi_name": "selectors2", "version": "1.1.1", "version_constraints": ">1.0,<2.0"}
-
-# Added these bugfix commits from 2.1.0:
-# * https://github.com/SethMichaelLarson/selectors2/commit/3bd74f2033363b606e1e849528ccaa76f5067590
-# Wrap kqueue.control so that timeout is a keyword arg
-# * https://github.com/SethMichaelLarson/selectors2/commit/6f6a26f42086d8aab273b30be492beecb373646b
-# Fix formatting of the kqueue.control patch for pylint
-# * https://github.com/SethMichaelLarson/selectors2/commit/f0c2c6c66cfa7662bc52beaf4e2d65adfa25e189
-# Fix use of OSError exception for py3 and use the wrapper of kqueue.control so retries of
-# interrupted syscalls work with kqueue
+from __future__ import annotations
+import selectors
import sys
-import types # pylint: disable=unused-import
-try:
- # Python 3.4+
- import selectors as _system_selectors
-except ImportError:
- try:
- # backport package installed in the system
- import selectors2 as _system_selectors # type: ignore[no-redef]
- except ImportError:
- _system_selectors = None # type: types.ModuleType | None # type: ignore[no-redef]
+from ansible.module_utils.common.warnings import deprecate
+
-if _system_selectors:
- selectors = _system_selectors
-else:
- # Our bundled copy
- from ansible.module_utils.compat import _selectors2 as selectors # type: ignore[no-redef]
sys.modules['ansible.module_utils.compat.selectors'] = selectors
+
+
+deprecate(
+ msg='The `ansible.module_utils.compat.selectors` module is deprecated.',
+ version='2.19',
+)
diff --git a/lib/ansible/module_utils/compat/selinux.py b/lib/ansible/module_utils/compat/selinux.py
index ca58098..0900388 100644
--- a/lib/ansible/module_utils/compat/selinux.py
+++ b/lib/ansible/module_utils/compat/selinux.py
@@ -1,8 +1,7 @@
# Copyright: (c) 2021, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import sys
diff --git a/lib/ansible/module_utils/compat/typing.py b/lib/ansible/module_utils/compat/typing.py
index 94b1dee..d753f72 100644
--- a/lib/ansible/module_utils/compat/typing.py
+++ b/lib/ansible/module_utils/compat/typing.py
@@ -1,6 +1,5 @@
"""Compatibility layer for the `typing` module, providing all Python versions access to the newest type-hinting features."""
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
# pylint: disable=wildcard-import,unused-wildcard-import
diff --git a/lib/ansible/module_utils/compat/version.py b/lib/ansible/module_utils/compat/version.py
index f4db1ef..61a39df 100644
--- a/lib/ansible/module_utils/compat/version.py
+++ b/lib/ansible/module_utils/compat/version.py
@@ -25,8 +25,7 @@ Every version number class implements the following interface:
of the same class, thus must follow the same rules)
"""
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/connection.py b/lib/ansible/module_utils/connection.py
index e4e507d..cc88969 100644
--- a/lib/ansible/module_utils/connection.py
+++ b/lib/ansible/module_utils/connection.py
@@ -26,8 +26,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import hashlib
diff --git a/lib/ansible/module_utils/csharp/Ansible.Basic.cs b/lib/ansible/module_utils/csharp/Ansible.Basic.cs
index 97f5f3e..a042af8 100644
--- a/lib/ansible/module_utils/csharp/Ansible.Basic.cs
+++ b/lib/ansible/module_utils/csharp/Ansible.Basic.cs
@@ -49,6 +49,7 @@ namespace Ansible.Basic
private static List<string> BOOLEANS_TRUE = new List<string>() { "y", "yes", "on", "1", "true", "t", "1.0" };
private static List<string> BOOLEANS_FALSE = new List<string>() { "n", "no", "off", "0", "false", "f", "0.0" };
+ private bool ignoreUnknownOpts = false;
private string remoteTmp = Path.GetTempPath();
private string tmpdir = null;
private HashSet<string> noLogValues = new HashSet<string>();
@@ -60,10 +61,12 @@ namespace Ansible.Basic
private Dictionary<string, string> passVars = new Dictionary<string, string>()
{
// null values means no mapping, not used in Ansible.Basic.AnsibleModule
+ // keep in sync with python counterpart in lib/ansible/module_utils/common/parameters.py
{ "check_mode", "CheckMode" },
{ "debug", "DebugMode" },
{ "diff", "DiffMode" },
{ "keep_remote_files", "KeepRemoteFiles" },
+ { "ignore_unknown_opts", "ignoreUnknownOpts" },
{ "module_name", "ModuleName" },
{ "no_log", "NoLog" },
{ "remote_tmp", "remoteTmp" },
@@ -72,11 +75,12 @@ namespace Ansible.Basic
{ "socket", null },
{ "string_conversion_action", null },
{ "syslog_facility", null },
+ { "target_log_info", "TargetLogInfo"},
{ "tmpdir", "tmpdir" },
{ "verbosity", "Verbosity" },
{ "version", "AnsibleVersion" },
};
- private List<string> passBools = new List<string>() { "check_mode", "debug", "diff", "keep_remote_files", "no_log" };
+ private List<string> passBools = new List<string>() { "check_mode", "debug", "diff", "keep_remote_files", "ignore_unknown_opts", "no_log" };
private List<string> passInts = new List<string>() { "verbosity" };
private Dictionary<string, List<object>> specDefaults = new Dictionary<string, List<object>>()
{
@@ -125,6 +129,7 @@ namespace Ansible.Basic
public bool KeepRemoteFiles { get; private set; }
public string ModuleName { get; private set; }
public bool NoLog { get; private set; }
+ public string TargetLogInfo { get; private set; }
public int Verbosity { get; private set; }
public string AnsibleVersion { get; private set; }
@@ -257,6 +262,7 @@ namespace Ansible.Basic
DiffMode = false;
KeepRemoteFiles = false;
ModuleName = "undefined win module";
+ TargetLogInfo = "";
NoLog = (bool)argumentSpec["no_log"];
Verbosity = 0;
AppDomain.CurrentDomain.ProcessExit += CleanupFiles;
@@ -372,9 +378,20 @@ namespace Ansible.Basic
logSource = "Application";
}
}
+
+ if (String.IsNullOrWhiteSpace(TargetLogInfo))
+ {
+ message = String.Format("{0} - {1}", ModuleName, message);
+ }
+ else
+ {
+ message = String.Format("{0} {1} - {2}", ModuleName, TargetLogInfo, message);
+ }
+
if (sanitise)
+ {
message = (string)RemoveNoLogValues(message, noLogValues);
- message = String.Format("{0} - {1}", ModuleName, message);
+ }
using (EventLog eventLog = new EventLog("Application"))
{
@@ -1043,7 +1060,7 @@ namespace Ansible.Basic
foreach (string parameter in removedParameters)
param.Remove(parameter);
- if (unsupportedParameters.Count > 0)
+ if (unsupportedParameters.Count > 0 && !ignoreUnknownOpts)
{
legalInputs.RemoveAll(x => passVars.Keys.Contains(x.Replace("_ansible_", "")));
string msg = String.Format("Unsupported parameters for ({0}) module: {1}", ModuleName, String.Join(", ", unsupportedParameters));
diff --git a/lib/ansible/module_utils/distro/__init__.py b/lib/ansible/module_utils/distro/__init__.py
index b70f29c..bed0b5a 100644
--- a/lib/ansible/module_utils/distro/__init__.py
+++ b/lib/ansible/module_utils/distro/__init__.py
@@ -15,15 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
'''
Compat distro library.
'''
+from __future__ import annotations
+
# The following makes it easier for us to script updates of the bundled code
-_BUNDLED_METADATA = {"pypi_name": "distro", "version": "1.6.0"}
+_BUNDLED_METADATA = {"pypi_name": "distro", "version": "1.8.0"}
# The following additional changes have been made:
# * Remove optparse since it is not needed for our use.
diff --git a/lib/ansible/module_utils/distro/_distro.py b/lib/ansible/module_utils/distro/_distro.py
index 19262a4..e57d6b6 100644
--- a/lib/ansible/module_utils/distro/_distro.py
+++ b/lib/ansible/module_utils/distro/_distro.py
@@ -30,6 +30,7 @@ Python 2.6 and removed in Python 3.8. Still, there are many cases in which
access to OS distribution information is needed. See `Python issue 1322
<https://bugs.python.org/issue1322>`_ for more information.
"""
+from __future__ import annotations
import argparse
import json
@@ -40,40 +41,39 @@ import shlex
import subprocess
import sys
import warnings
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Optional,
+ Sequence,
+ TextIO,
+ Tuple,
+ Type,
+)
-__version__ = "1.6.0"
-
-# Use `if False` to avoid an ImportError on Python 2. After dropping Python 2
-# support, can use typing.TYPE_CHECKING instead. See:
-# https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
-if False: # pragma: nocover
- from typing import (
- Any,
- Callable,
- Dict,
- Iterable,
- Optional,
- Sequence,
- TextIO,
- Tuple,
- Type,
- TypedDict,
- Union,
- )
+try:
+ from typing import TypedDict
+except ImportError:
+ # Python 3.7
+ TypedDict = dict
- VersionDict = TypedDict(
- "VersionDict", {"major": str, "minor": str, "build_number": str}
- )
- InfoDict = TypedDict(
- "InfoDict",
- {
- "id": str,
- "version": str,
- "version_parts": VersionDict,
- "like": str,
- "codename": str,
- },
- )
+__version__ = "1.8.0"
+
+
+class VersionDict(TypedDict):
+ major: str
+ minor: str
+ build_number: str
+
+
+class InfoDict(TypedDict):
+ id: str
+ version: str
+ version_parts: VersionDict
+ like: str
+ codename: str
_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
@@ -126,6 +126,26 @@ _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
# Pattern for base file name of distro release file
_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$")
+# Base file names to be looked up for if _UNIXCONFDIR is not readable.
+_DISTRO_RELEASE_BASENAMES = [
+ "SuSE-release",
+ "arch-release",
+ "base-release",
+ "centos-release",
+ "fedora-release",
+ "gentoo-release",
+ "mageia-release",
+ "mandrake-release",
+ "mandriva-release",
+ "mandrivalinux-release",
+ "manjaro-release",
+ "oracle-release",
+ "redhat-release",
+ "rocky-release",
+ "sl-release",
+ "slackware-version",
+]
+
# Base file names to be ignored when searching for distro release file
_DISTRO_RELEASE_IGNORE_BASENAMES = (
"debian_version",
@@ -138,8 +158,7 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = (
)
-def linux_distribution(full_distribution_name=True):
- # type: (bool) -> Tuple[str, str, str]
+def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
"""
.. deprecated:: 1.6.0
@@ -182,8 +201,7 @@ def linux_distribution(full_distribution_name=True):
return _distro.linux_distribution(full_distribution_name)
-def id():
- # type: () -> str
+def id() -> str:
"""
Return the distro ID of the current distribution, as a
machine-readable string.
@@ -227,6 +245,7 @@ def id():
"freebsd" FreeBSD
"midnightbsd" MidnightBSD
"rocky" Rocky Linux
+ "aix" AIX
"guix" Guix System
============== =========================================
@@ -265,8 +284,7 @@ def id():
return _distro.id()
-def name(pretty=False):
- # type: (bool) -> str
+def name(pretty: bool = False) -> str:
"""
Return the name of the current OS distribution, as a human-readable
string.
@@ -305,8 +323,7 @@ def name(pretty=False):
return _distro.name(pretty)
-def version(pretty=False, best=False):
- # type: (bool, bool) -> str
+def version(pretty: bool = False, best: bool = False) -> str:
"""
Return the version of the current OS distribution, as a human-readable
string.
@@ -354,8 +371,7 @@ def version(pretty=False, best=False):
return _distro.version(pretty, best)
-def version_parts(best=False):
- # type: (bool) -> Tuple[str, str, str]
+def version_parts(best: bool = False) -> Tuple[str, str, str]:
"""
Return the version of the current OS distribution as a tuple
``(major, minor, build_number)`` with items as follows:
@@ -372,8 +388,7 @@ def version_parts(best=False):
return _distro.version_parts(best)
-def major_version(best=False):
- # type: (bool) -> str
+def major_version(best: bool = False) -> str:
"""
Return the major version of the current OS distribution, as a string,
if provided.
@@ -386,8 +401,7 @@ def major_version(best=False):
return _distro.major_version(best)
-def minor_version(best=False):
- # type: (bool) -> str
+def minor_version(best: bool = False) -> str:
"""
Return the minor version of the current OS distribution, as a string,
if provided.
@@ -400,8 +414,7 @@ def minor_version(best=False):
return _distro.minor_version(best)
-def build_number(best=False):
- # type: (bool) -> str
+def build_number(best: bool = False) -> str:
"""
Return the build number of the current OS distribution, as a string,
if provided.
@@ -414,8 +427,7 @@ def build_number(best=False):
return _distro.build_number(best)
-def like():
- # type: () -> str
+def like() -> str:
"""
Return a space-separated list of distro IDs of distributions that are
closely related to the current OS distribution in regards to packaging
@@ -432,8 +444,7 @@ def like():
return _distro.like()
-def codename():
- # type: () -> str
+def codename() -> str:
"""
Return the codename for the release of the current OS distribution,
as a string.
@@ -457,8 +468,7 @@ def codename():
return _distro.codename()
-def info(pretty=False, best=False):
- # type: (bool, bool) -> InfoDict
+def info(pretty: bool = False, best: bool = False) -> InfoDict:
"""
Return certain machine-readable information items about the current OS
distribution in a dictionary, as shown in the following example:
@@ -502,8 +512,7 @@ def info(pretty=False, best=False):
return _distro.info(pretty, best)
-def os_release_info():
- # type: () -> Dict[str, str]
+def os_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the os-release file data source of the current OS distribution.
@@ -513,8 +522,7 @@ def os_release_info():
return _distro.os_release_info()
-def lsb_release_info():
- # type: () -> Dict[str, str]
+def lsb_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the lsb_release command data source of the current OS distribution.
@@ -525,8 +533,7 @@ def lsb_release_info():
return _distro.lsb_release_info()
-def distro_release_info():
- # type: () -> Dict[str, str]
+def distro_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the distro release file data source of the current OS distribution.
@@ -536,8 +543,7 @@ def distro_release_info():
return _distro.distro_release_info()
-def uname_info():
- # type: () -> Dict[str, str]
+def uname_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the distro release file data source of the current OS distribution.
@@ -545,8 +551,7 @@ def uname_info():
return _distro.uname_info()
-def os_release_attr(attribute):
- # type: (str) -> str
+def os_release_attr(attribute: str) -> str:
"""
Return a single named information item from the os-release file data source
of the current OS distribution.
@@ -565,8 +570,7 @@ def os_release_attr(attribute):
return _distro.os_release_attr(attribute)
-def lsb_release_attr(attribute):
- # type: (str) -> str
+def lsb_release_attr(attribute: str) -> str:
"""
Return a single named information item from the lsb_release command output
data source of the current OS distribution.
@@ -586,8 +590,7 @@ def lsb_release_attr(attribute):
return _distro.lsb_release_attr(attribute)
-def distro_release_attr(attribute):
- # type: (str) -> str
+def distro_release_attr(attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the current OS distribution.
@@ -606,8 +609,7 @@ def distro_release_attr(attribute):
return _distro.distro_release_attr(attribute)
-def uname_attr(attribute):
- # type: (str) -> str
+def uname_attr(attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the current OS distribution.
@@ -628,25 +630,23 @@ try:
from functools import cached_property
except ImportError:
# Python < 3.8
- class cached_property(object): # type: ignore
+ class cached_property: # type: ignore
"""A version of @property which caches the value. On access, it calls the
underlying function and sets the value in `__dict__` so future accesses
will not re-call the property.
"""
- def __init__(self, f):
- # type: (Callable[[Any], Any]) -> None
+ def __init__(self, f: Callable[[Any], Any]) -> None:
self._fname = f.__name__
self._f = f
- def __get__(self, obj, owner):
- # type: (Any, Type[Any]) -> Any
- assert obj is not None, "call {} on an instance".format(self._fname)
+ def __get__(self, obj: Any, owner: Type[Any]) -> Any:
+ assert obj is not None, f"call {self._fname} on an instance"
ret = obj.__dict__[self._fname] = self._f(obj)
return ret
-class LinuxDistribution(object):
+class LinuxDistribution:
"""
Provides information about a OS distribution.
@@ -666,13 +666,13 @@ class LinuxDistribution(object):
def __init__(
self,
- include_lsb=True,
- os_release_file="",
- distro_release_file="",
- include_uname=True,
- root_dir=None,
- ):
- # type: (bool, str, str, bool, Optional[str]) -> None
+ include_lsb: Optional[bool] = None,
+ os_release_file: str = "",
+ distro_release_file: str = "",
+ include_uname: Optional[bool] = None,
+ root_dir: Optional[str] = None,
+ include_oslevel: Optional[bool] = None,
+ ) -> None:
"""
The initialization method of this class gathers information from the
available data sources, and stores that in private instance attributes.
@@ -712,7 +712,13 @@ class LinuxDistribution(object):
be empty.
* ``root_dir`` (string): The absolute path to the root directory to use
- to find distro-related information files.
+ to find distro-related information files. Note that ``include_*``
+ parameters must not be enabled in combination with ``root_dir``.
+
+ * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
+ output is included as a data source. If the oslevel command is not
+ available in the program execution path the data source will be
+ empty.
Public instance attributes:
@@ -731,9 +737,20 @@ class LinuxDistribution(object):
parameter. This controls whether the uname information will
be loaded.
+ * ``include_oslevel`` (bool): The result of the ``include_oslevel``
+ parameter. This controls whether (AIX) oslevel information will be
+ loaded.
+
+ * ``root_dir`` (string): The result of the ``root_dir`` parameter.
+ The absolute path to the root directory to use to find distro-related
+ information files.
+
Raises:
- * :py:exc:`IOError`: Some I/O issue with an os-release file or distro
+ * :py:exc:`ValueError`: Initialization parameters combination is not
+ supported.
+
+ * :py:exc:`OSError`: Some I/O issue with an os-release file or distro
release file.
* :py:exc:`UnicodeError`: A data source has unexpected characters or
@@ -763,11 +780,24 @@ class LinuxDistribution(object):
self.os_release_file = usr_lib_os_release_file
self.distro_release_file = distro_release_file or "" # updated later
- self.include_lsb = include_lsb
- self.include_uname = include_uname
- def __repr__(self):
- # type: () -> str
+ is_root_dir_defined = root_dir is not None
+ if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
+ raise ValueError(
+ "Including subprocess data sources from specific root_dir is disallowed"
+ " to prevent false information"
+ )
+ self.include_lsb = (
+ include_lsb if include_lsb is not None else not is_root_dir_defined
+ )
+ self.include_uname = (
+ include_uname if include_uname is not None else not is_root_dir_defined
+ )
+ self.include_oslevel = (
+ include_oslevel if include_oslevel is not None else not is_root_dir_defined
+ )
+
+ def __repr__(self) -> str:
"""Return repr of all info"""
return (
"LinuxDistribution("
@@ -775,14 +805,18 @@ class LinuxDistribution(object):
"distro_release_file={self.distro_release_file!r}, "
"include_lsb={self.include_lsb!r}, "
"include_uname={self.include_uname!r}, "
+ "include_oslevel={self.include_oslevel!r}, "
+ "root_dir={self.root_dir!r}, "
"_os_release_info={self._os_release_info!r}, "
"_lsb_release_info={self._lsb_release_info!r}, "
"_distro_release_info={self._distro_release_info!r}, "
- "_uname_info={self._uname_info!r})".format(self=self)
+ "_uname_info={self._uname_info!r}, "
+ "_oslevel_info={self._oslevel_info!r})".format(self=self)
)
- def linux_distribution(self, full_distribution_name=True):
- # type: (bool) -> Tuple[str, str, str]
+ def linux_distribution(
+ self, full_distribution_name: bool = True
+ ) -> Tuple[str, str, str]:
"""
Return information about the OS distribution that is compatible
with Python's :func:`platform.linux_distribution`, supporting a subset
@@ -796,15 +830,13 @@ class LinuxDistribution(object):
self._os_release_info.get("release_codename") or self.codename(),
)
- def id(self):
- # type: () -> str
+ def id(self) -> str:
"""Return the distro ID of the OS distribution, as a string.
For details, see :func:`distro.id`.
"""
- def normalize(distro_id, table):
- # type: (str, Dict[str, str]) -> str
+ def normalize(distro_id: str, table: Dict[str, str]) -> str:
distro_id = distro_id.lower().replace(" ", "_")
return table.get(distro_id, distro_id)
@@ -826,8 +858,7 @@ class LinuxDistribution(object):
return ""
- def name(self, pretty=False):
- # type: (bool) -> str
+ def name(self, pretty: bool = False) -> str:
"""
Return the name of the OS distribution, as a string.
@@ -847,11 +878,10 @@ class LinuxDistribution(object):
name = self.distro_release_attr("name") or self.uname_attr("name")
version = self.version(pretty=True)
if version:
- name = name + " " + version
+ name = f"{name} {version}"
return name or ""
- def version(self, pretty=False, best=False):
- # type: (bool, bool) -> str
+ def version(self, pretty: bool = False, best: bool = False) -> str:
"""
Return the version of the OS distribution, as a string.
@@ -869,7 +899,10 @@ class LinuxDistribution(object):
).get("version_id", ""),
self.uname_attr("release"),
]
- if self.id() == "debian" or "debian" in self.like().split():
+ if self.uname_attr("id").startswith("aix"):
+ # On AIX platforms, prefer oslevel command output.
+ versions.insert(0, self.oslevel_info())
+ elif self.id() == "debian" or "debian" in self.like().split():
# On Debian-like, add debian_version file content to candidates list.
versions.append(self._debian_version)
version = ""
@@ -887,11 +920,10 @@ class LinuxDistribution(object):
version = v
break
if pretty and version and self.codename():
- version = "{0} ({1})".format(version, self.codename())
+ version = f"{version} ({self.codename()})"
return version
- def version_parts(self, best=False):
- # type: (bool) -> Tuple[str, str, str]
+ def version_parts(self, best: bool = False) -> Tuple[str, str, str]:
"""
Return the version of the OS distribution, as a tuple of version
numbers.
@@ -907,8 +939,7 @@ class LinuxDistribution(object):
return major, minor or "", build_number or ""
return "", "", ""
- def major_version(self, best=False):
- # type: (bool) -> str
+ def major_version(self, best: bool = False) -> str:
"""
Return the major version number of the current distribution.
@@ -916,8 +947,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[0]
- def minor_version(self, best=False):
- # type: (bool) -> str
+ def minor_version(self, best: bool = False) -> str:
"""
Return the minor version number of the current distribution.
@@ -925,8 +955,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[1]
- def build_number(self, best=False):
- # type: (bool) -> str
+ def build_number(self, best: bool = False) -> str:
"""
Return the build number of the current distribution.
@@ -934,8 +963,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[2]
- def like(self):
- # type: () -> str
+ def like(self) -> str:
"""
Return the IDs of distributions that are like the OS distribution.
@@ -943,8 +971,7 @@ class LinuxDistribution(object):
"""
return self.os_release_attr("id_like") or ""
- def codename(self):
- # type: () -> str
+ def codename(self) -> str:
"""
Return the codename of the OS distribution.
@@ -961,8 +988,7 @@ class LinuxDistribution(object):
or ""
)
- def info(self, pretty=False, best=False):
- # type: (bool, bool) -> InfoDict
+ def info(self, pretty: bool = False, best: bool = False) -> InfoDict:
"""
Return certain machine-readable information about the OS
distribution.
@@ -981,8 +1007,7 @@ class LinuxDistribution(object):
codename=self.codename(),
)
- def os_release_info(self):
- # type: () -> Dict[str, str]
+ def os_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the os-release file data source of the OS distribution.
@@ -991,8 +1016,7 @@ class LinuxDistribution(object):
"""
return self._os_release_info
- def lsb_release_info(self):
- # type: () -> Dict[str, str]
+ def lsb_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the lsb_release command data source of the OS
@@ -1002,8 +1026,7 @@ class LinuxDistribution(object):
"""
return self._lsb_release_info
- def distro_release_info(self):
- # type: () -> Dict[str, str]
+ def distro_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the distro release file data source of the OS
@@ -1013,8 +1036,7 @@ class LinuxDistribution(object):
"""
return self._distro_release_info
- def uname_info(self):
- # type: () -> Dict[str, str]
+ def uname_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the uname command data source of the OS distribution.
@@ -1023,8 +1045,13 @@ class LinuxDistribution(object):
"""
return self._uname_info
- def os_release_attr(self, attribute):
- # type: (str) -> str
+ def oslevel_info(self) -> str:
+ """
+ Return AIX' oslevel command output.
+ """
+ return self._oslevel_info
+
+ def os_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the os-release file data
source of the OS distribution.
@@ -1033,8 +1060,7 @@ class LinuxDistribution(object):
"""
return self._os_release_info.get(attribute, "")
- def lsb_release_attr(self, attribute):
- # type: (str) -> str
+ def lsb_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the lsb_release command
output data source of the OS distribution.
@@ -1043,8 +1069,7 @@ class LinuxDistribution(object):
"""
return self._lsb_release_info.get(attribute, "")
- def distro_release_attr(self, attribute):
- # type: (str) -> str
+ def distro_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the OS distribution.
@@ -1053,8 +1078,7 @@ class LinuxDistribution(object):
"""
return self._distro_release_info.get(attribute, "")
- def uname_attr(self, attribute):
- # type: (str) -> str
+ def uname_attr(self, attribute: str) -> str:
"""
Return a single named information item from the uname command
output data source of the OS distribution.
@@ -1064,8 +1088,7 @@ class LinuxDistribution(object):
return self._uname_info.get(attribute, "")
@cached_property
- def _os_release_info(self):
- # type: () -> Dict[str, str]
+ def _os_release_info(self) -> Dict[str, str]:
"""
Get the information items from the specified os-release file.
@@ -1073,13 +1096,12 @@ class LinuxDistribution(object):
A dictionary containing all information items.
"""
if os.path.isfile(self.os_release_file):
- with open(self.os_release_file) as release_file:
+ with open(self.os_release_file, encoding="utf-8") as release_file:
return self._parse_os_release_content(release_file)
return {}
@staticmethod
- def _parse_os_release_content(lines):
- # type: (TextIO) -> Dict[str, str]
+ def _parse_os_release_content(lines: TextIO) -> Dict[str, str]:
"""
Parse the lines of an os-release file.
@@ -1096,16 +1118,6 @@ class LinuxDistribution(object):
lexer = shlex.shlex(lines, posix=True)
lexer.whitespace_split = True
- # The shlex module defines its `wordchars` variable using literals,
- # making it dependent on the encoding of the Python source file.
- # In Python 2.6 and 2.7, the shlex source file is encoded in
- # 'iso-8859-1', and the `wordchars` variable is defined as a byte
- # string. This causes a UnicodeDecodeError to be raised when the
- # parsed content is a unicode object. The following fix resolves that
- # (... but it should be fixed in shlex...):
- if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
- lexer.wordchars = lexer.wordchars.decode("iso-8859-1")
-
tokens = list(lexer)
for token in tokens:
# At this point, all shell-like parsing has been done (i.e.
@@ -1139,8 +1151,7 @@ class LinuxDistribution(object):
return props
@cached_property
- def _lsb_release_info(self):
- # type: () -> Dict[str, str]
+ def _lsb_release_info(self) -> Dict[str, str]:
"""
Get the information items from the lsb_release command output.
@@ -1149,19 +1160,17 @@ class LinuxDistribution(object):
"""
if not self.include_lsb:
return {}
- with open(os.devnull, "wb") as devnull:
- try:
- cmd = ("lsb_release", "-a")
- stdout = subprocess.check_output(cmd, stderr=devnull)
- # Command not found or lsb_release returned error
- except (OSError, subprocess.CalledProcessError):
- return {}
+ try:
+ cmd = ("lsb_release", "-a")
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
+ # Command not found or lsb_release returned error
+ except (OSError, subprocess.CalledProcessError):
+ return {}
content = self._to_str(stdout).splitlines()
return self._parse_lsb_release_content(content)
@staticmethod
- def _parse_lsb_release_content(lines):
- # type: (Iterable[str]) -> Dict[str, str]
+ def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]:
"""
Parse the output of the lsb_release command.
@@ -1185,31 +1194,39 @@ class LinuxDistribution(object):
return props
@cached_property
- def _uname_info(self):
- # type: () -> Dict[str, str]
+ def _uname_info(self) -> Dict[str, str]:
if not self.include_uname:
return {}
- with open(os.devnull, "wb") as devnull:
- try:
- cmd = ("uname", "-rs")
- stdout = subprocess.check_output(cmd, stderr=devnull)
- except OSError:
- return {}
+ try:
+ cmd = ("uname", "-rs")
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
+ except OSError:
+ return {}
content = self._to_str(stdout).splitlines()
return self._parse_uname_content(content)
@cached_property
- def _debian_version(self):
- # type: () -> str
+ def _oslevel_info(self) -> str:
+ if not self.include_oslevel:
+ return ""
+ try:
+ stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL)
+ except (OSError, subprocess.CalledProcessError):
+ return ""
+ return self._to_str(stdout).strip()
+
+ @cached_property
+ def _debian_version(self) -> str:
try:
- with open(os.path.join(self.etc_dir, "debian_version")) as fp:
+ with open(
+ os.path.join(self.etc_dir, "debian_version"), encoding="ascii"
+ ) as fp:
return fp.readline().rstrip()
- except (OSError, IOError):
+ except FileNotFoundError:
return ""
@staticmethod
- def _parse_uname_content(lines):
- # type: (Sequence[str]) -> Dict[str, str]
+ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
if not lines:
return {}
props = {}
@@ -1228,23 +1245,12 @@ class LinuxDistribution(object):
return props
@staticmethod
- def _to_str(text):
- # type: (Union[bytes, str]) -> str
+ def _to_str(bytestring: bytes) -> str:
encoding = sys.getfilesystemencoding()
- encoding = "utf-8" if encoding == "ascii" else encoding
-
- if sys.version_info[0] >= 3:
- if isinstance(text, bytes):
- return text.decode(encoding)
- else:
- if isinstance(text, unicode): # noqa
- return text.encode(encoding)
-
- return text
+ return bytestring.decode(encoding)
@cached_property
- def _distro_release_info(self):
- # type: () -> Dict[str, str]
+ def _distro_release_info(self) -> Dict[str, str]:
"""
Get the information items from the specified distro release file.
@@ -1261,14 +1267,14 @@ class LinuxDistribution(object):
# file), because we want to use what was specified as best as
# possible.
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
- if "name" in distro_info and "cloudlinux" in distro_info["name"].lower():
- distro_info["id"] = "cloudlinux"
- elif match:
- distro_info["id"] = match.group(1)
- return distro_info
else:
try:
- basenames = os.listdir(self.etc_dir)
+ basenames = [
+ basename
+ for basename in os.listdir(self.etc_dir)
+ if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
+ and os.path.isfile(os.path.join(self.etc_dir, basename))
+ ]
# We sort for repeatability in cases where there are multiple
# distro specific files; e.g. CentOS, Oracle, Enterprise all
# containing `redhat-release` on top of their own.
@@ -1278,42 +1284,31 @@ class LinuxDistribution(object):
# sure about the *-release files. Check common entries of
# /etc for information. If they turn out to not be there the
# error is handled in `_parse_distro_release_file()`.
- basenames = [
- "SuSE-release",
- "arch-release",
- "base-release",
- "centos-release",
- "fedora-release",
- "gentoo-release",
- "mageia-release",
- "mandrake-release",
- "mandriva-release",
- "mandrivalinux-release",
- "manjaro-release",
- "oracle-release",
- "redhat-release",
- "rocky-release",
- "sl-release",
- "slackware-version",
- ]
+ basenames = _DISTRO_RELEASE_BASENAMES
for basename in basenames:
- if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:
- continue
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
- if match:
- filepath = os.path.join(self.etc_dir, basename)
- distro_info = self._parse_distro_release_file(filepath)
- if "name" in distro_info:
- # The name is always present if the pattern matches
- self.distro_release_file = filepath
- distro_info["id"] = match.group(1)
- if "cloudlinux" in distro_info["name"].lower():
- distro_info["id"] = "cloudlinux"
- return distro_info
- return {}
+ if match is None:
+ continue
+ filepath = os.path.join(self.etc_dir, basename)
+ distro_info = self._parse_distro_release_file(filepath)
+ # The name is always present if the pattern matches.
+ if "name" not in distro_info:
+ continue
+ self.distro_release_file = filepath
+ break
+ else: # the loop didn't "break": no candidate.
+ return {}
+
+ if match is not None:
+ distro_info["id"] = match.group(1)
+
+ # CloudLinux < 7: manually enrich info with proper id.
+ if "cloudlinux" in distro_info.get("name", "").lower():
+ distro_info["id"] = "cloudlinux"
+
+ return distro_info
- def _parse_distro_release_file(self, filepath):
- # type: (str) -> Dict[str, str]
+ def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]:
"""
Parse a distro release file.
@@ -1325,19 +1320,18 @@ class LinuxDistribution(object):
A dictionary containing all information items.
"""
try:
- with open(filepath) as fp:
+ with open(filepath, encoding="utf-8") as fp:
# Only parse the first line. For instance, on SLES there
# are multiple lines. We don't want them...
return self._parse_distro_release_content(fp.readline())
- except (OSError, IOError):
+ except OSError:
# Ignore not being able to read a specific, seemingly version
# related file.
# See https://github.com/python-distro/distro/issues/162
return {}
@staticmethod
- def _parse_distro_release_content(line):
- # type: (str) -> Dict[str, str]
+ def _parse_distro_release_content(line: str) -> Dict[str, str]:
"""
Parse a line from a distro release file.
@@ -1365,8 +1359,7 @@ class LinuxDistribution(object):
_distro = LinuxDistribution()
-def main():
- # type: () -> None
+def main() -> None:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
@@ -1388,7 +1381,10 @@ def main():
if args.root_dir:
dist = LinuxDistribution(
- include_lsb=False, include_uname=False, root_dir=args.root_dir
+ include_lsb=False,
+ include_uname=False,
+ include_oslevel=False,
+ root_dir=args.root_dir,
)
else:
dist = _distro
diff --git a/lib/ansible/module_utils/errors.py b/lib/ansible/module_utils/errors.py
index cbbd86c..1196fac 100644
--- a/lib/ansible/module_utils/errors.py
+++ b/lib/ansible/module_utils/errors.py
@@ -2,8 +2,7 @@
# Copyright (c) 2021 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
class AnsibleFallbackNotFound(Exception):
diff --git a/lib/ansible/module_utils/facts/__init__.py b/lib/ansible/module_utils/facts/__init__.py
index 96ab778..6d24691 100644
--- a/lib/ansible/module_utils/facts/__init__.py
+++ b/lib/ansible/module_utils/facts/__init__.py
@@ -26,8 +26,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
# import from the compat api because 2.0-2.3 had a module_utils.facts.ansible_facts
# and get_all_facts in top level namespace
diff --git a/lib/ansible/module_utils/facts/ansible_collector.py b/lib/ansible/module_utils/facts/ansible_collector.py
index e9bafe2..ac81d1f 100644
--- a/lib/ansible/module_utils/facts/ansible_collector.py
+++ b/lib/ansible/module_utils/facts/ansible_collector.py
@@ -26,8 +26,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import fnmatch
import sys
diff --git a/lib/ansible/module_utils/facts/collector.py b/lib/ansible/module_utils/facts/collector.py
index ac52fe8..616188b 100644
--- a/lib/ansible/module_utils/facts/collector.py
+++ b/lib/ansible/module_utils/facts/collector.py
@@ -26,8 +26,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from collections import defaultdict
diff --git a/lib/ansible/module_utils/facts/compat.py b/lib/ansible/module_utils/facts/compat.py
index a69fee3..3895314 100644
--- a/lib/ansible/module_utils/facts/compat.py
+++ b/lib/ansible/module_utils/facts/compat.py
@@ -26,8 +26,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.namespace import PrefixFactNamespace
from ansible.module_utils.facts import default_collectors
diff --git a/lib/ansible/module_utils/facts/default_collectors.py b/lib/ansible/module_utils/facts/default_collectors.py
index cf0ef23..1dcbd7c 100644
--- a/lib/ansible/module_utils/facts/default_collectors.py
+++ b/lib/ansible/module_utils/facts/default_collectors.py
@@ -25,8 +25,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/hardware/aix.py b/lib/ansible/module_utils/facts/hardware/aix.py
index dc37394..db34fe1 100644
--- a/lib/ansible/module_utils/facts/hardware/aix.py
+++ b/lib/ansible/module_utils/facts/hardware/aix.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/facts/hardware/base.py b/lib/ansible/module_utils/facts/hardware/base.py
index 846bb30..8710ed5 100644
--- a/lib/ansible/module_utils/facts/hardware/base.py
+++ b/lib/ansible/module_utils/facts/hardware/base.py
@@ -26,8 +26,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/hardware/darwin.py b/lib/ansible/module_utils/facts/hardware/darwin.py
index d6a8e11..74e4ce4 100644
--- a/lib/ansible/module_utils/facts/hardware/darwin.py
+++ b/lib/ansible/module_utils/facts/hardware/darwin.py
@@ -14,8 +14,7 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import struct
import time
diff --git a/lib/ansible/module_utils/facts/hardware/dragonfly.py b/lib/ansible/module_utils/facts/hardware/dragonfly.py
index ea24151..ffbde72 100644
--- a/lib/ansible/module_utils/facts/hardware/dragonfly.py
+++ b/lib/ansible/module_utils/facts/hardware/dragonfly.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.hardware.base import HardwareCollector
from ansible.module_utils.facts.hardware.freebsd import FreeBSDHardware
diff --git a/lib/ansible/module_utils/facts/hardware/freebsd.py b/lib/ansible/module_utils/facts/hardware/freebsd.py
index cce2ab2..e44da3a 100644
--- a/lib/ansible/module_utils/facts/hardware/freebsd.py
+++ b/lib/ansible/module_utils/facts/hardware/freebsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import json
diff --git a/lib/ansible/module_utils/facts/hardware/hpux.py b/lib/ansible/module_utils/facts/hardware/hpux.py
index ae72ed8..abb9dad 100644
--- a/lib/ansible/module_utils/facts/hardware/hpux.py
+++ b/lib/ansible/module_utils/facts/hardware/hpux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import re
diff --git a/lib/ansible/module_utils/facts/hardware/hurd.py b/lib/ansible/module_utils/facts/hardware/hurd.py
index 306e13c..491670c 100644
--- a/lib/ansible/module_utils/facts/hardware/hurd.py
+++ b/lib/ansible/module_utils/facts/hardware/hurd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.timeout import TimeoutError
from ansible.module_utils.facts.hardware.base import HardwareCollector
diff --git a/lib/ansible/module_utils/facts/hardware/linux.py b/lib/ansible/module_utils/facts/hardware/linux.py
index 4e6305c..605dbe6 100644
--- a/lib/ansible/module_utils/facts/hardware/linux.py
+++ b/lib/ansible/module_utils/facts/hardware/linux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import collections
import errno
@@ -258,7 +257,7 @@ class LinuxHardware(Hardware):
if collected_facts.get('ansible_architecture') == 's390x':
# getting sockets would require 5.7+ with CONFIG_SCHED_TOPOLOGY
cpu_facts['processor_count'] = 1
- cpu_facts['processor_cores'] = zp // zmt
+ cpu_facts['processor_cores'] = round(zp / zmt)
cpu_facts['processor_threads_per_core'] = zmt
cpu_facts['processor_vcpus'] = zp
cpu_facts['processor_nproc'] = zp
@@ -283,9 +282,9 @@ class LinuxHardware(Hardware):
core_values = list(cores.values())
if core_values:
- cpu_facts['processor_threads_per_core'] = core_values[0] // cpu_facts['processor_cores']
+ cpu_facts['processor_threads_per_core'] = round(core_values[0] / cpu_facts['processor_cores'])
else:
- cpu_facts['processor_threads_per_core'] = 1 // cpu_facts['processor_cores']
+ cpu_facts['processor_threads_per_core'] = round(1 / cpu_facts['processor_cores'])
cpu_facts['processor_vcpus'] = (cpu_facts['processor_threads_per_core'] *
cpu_facts['processor_count'] * cpu_facts['processor_cores'])
@@ -556,6 +555,7 @@ class LinuxHardware(Hardware):
fields = [self._replace_octal_escapes(field) for field in fields]
device, mount, fstype, options = fields[0], fields[1], fields[2], fields[3]
+ dump, passno = int(fields[4]), int(fields[5])
if not device.startswith(('/', '\\')) and ':/' not in device or fstype == 'none':
continue
@@ -563,7 +563,9 @@ class LinuxHardware(Hardware):
mount_info = {'mount': mount,
'device': device,
'fstype': fstype,
- 'options': options}
+ 'options': options,
+ 'dump': dump,
+ 'passno': passno}
if mount in bind_mounts:
# only add if not already there, we might have a plain /etc/mtab
diff --git a/lib/ansible/module_utils/facts/hardware/netbsd.py b/lib/ansible/module_utils/facts/hardware/netbsd.py
index c6557aa..7d02419 100644
--- a/lib/ansible/module_utils/facts/hardware/netbsd.py
+++ b/lib/ansible/module_utils/facts/hardware/netbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import re
diff --git a/lib/ansible/module_utils/facts/hardware/openbsd.py b/lib/ansible/module_utils/facts/hardware/openbsd.py
index cd5e21e..751ee61 100644
--- a/lib/ansible/module_utils/facts/hardware/openbsd.py
+++ b/lib/ansible/module_utils/facts/hardware/openbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
import time
diff --git a/lib/ansible/module_utils/facts/hardware/sunos.py b/lib/ansible/module_utils/facts/hardware/sunos.py
index 54850fe..62eeafc 100644
--- a/lib/ansible/module_utils/facts/hardware/sunos.py
+++ b/lib/ansible/module_utils/facts/hardware/sunos.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
import time
@@ -108,7 +107,7 @@ class SunOSHardware(Hardware):
# Counting cores on Solaris can be complicated.
# https://blogs.oracle.com/mandalika/entry/solaris_show_me_the_cpu
# Treat 'processor_count' as physical sockets and 'processor_cores' as
- # virtual CPUs visisble to Solaris. Not a true count of cores for modern SPARC as
+ # virtual CPUs visible to Solaris. Not a true count of cores for modern SPARC as
# these processors have: sockets -> cores -> threads/virtual CPU.
if len(sockets) > 0:
cpu_facts['processor_count'] = len(sockets)
diff --git a/lib/ansible/module_utils/facts/namespace.py b/lib/ansible/module_utils/facts/namespace.py
index 2d6bf8a..3d0eb25 100644
--- a/lib/ansible/module_utils/facts/namespace.py
+++ b/lib/ansible/module_utils/facts/namespace.py
@@ -25,8 +25,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
class FactNamespace:
diff --git a/lib/ansible/module_utils/facts/network/aix.py b/lib/ansible/module_utils/facts/network/aix.py
index e9c90c6..29a679d 100644
--- a/lib/ansible/module_utils/facts/network/aix.py
+++ b/lib/ansible/module_utils/facts/network/aix.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/facts/network/base.py b/lib/ansible/module_utils/facts/network/base.py
index 8243f06..7e13e16 100644
--- a/lib/ansible/module_utils/facts/network/base.py
+++ b/lib/ansible/module_utils/facts/network/base.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/network/darwin.py b/lib/ansible/module_utils/facts/network/darwin.py
index 90117e5..775d407 100644
--- a/lib/ansible/module_utils/facts/network/darwin.py
+++ b/lib/ansible/module_utils/facts/network/darwin.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork
diff --git a/lib/ansible/module_utils/facts/network/dragonfly.py b/lib/ansible/module_utils/facts/network/dragonfly.py
index e43bbb2..8a34245 100644
--- a/lib/ansible/module_utils/facts/network/dragonfly.py
+++ b/lib/ansible/module_utils/facts/network/dragonfly.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork
diff --git a/lib/ansible/module_utils/facts/network/fc_wwn.py b/lib/ansible/module_utils/facts/network/fc_wwn.py
index dc2e3d6..f53cc53 100644
--- a/lib/ansible/module_utils/facts/network/fc_wwn.py
+++ b/lib/ansible/module_utils/facts/network/fc_wwn.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import sys
import glob
diff --git a/lib/ansible/module_utils/facts/network/freebsd.py b/lib/ansible/module_utils/facts/network/freebsd.py
index 36f6eec..4497010 100644
--- a/lib/ansible/module_utils/facts/network/freebsd.py
+++ b/lib/ansible/module_utils/facts/network/freebsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork
diff --git a/lib/ansible/module_utils/facts/network/generic_bsd.py b/lib/ansible/module_utils/facts/network/generic_bsd.py
index 8d640f2..5418863 100644
--- a/lib/ansible/module_utils/facts/network/generic_bsd.py
+++ b/lib/ansible/module_utils/facts/network/generic_bsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
import socket
diff --git a/lib/ansible/module_utils/facts/network/hpux.py b/lib/ansible/module_utils/facts/network/hpux.py
index add57be..61e1bdc 100644
--- a/lib/ansible/module_utils/facts/network/hpux.py
+++ b/lib/ansible/module_utils/facts/network/hpux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import Network, NetworkCollector
diff --git a/lib/ansible/module_utils/facts/network/hurd.py b/lib/ansible/module_utils/facts/network/hurd.py
index 518df39..05f23e5 100644
--- a/lib/ansible/module_utils/facts/network/hurd.py
+++ b/lib/ansible/module_utils/facts/network/hurd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/network/iscsi.py b/lib/ansible/module_utils/facts/network/iscsi.py
index ef5ac39..8f7a615 100644
--- a/lib/ansible/module_utils/facts/network/iscsi.py
+++ b/lib/ansible/module_utils/facts/network/iscsi.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import sys
diff --git a/lib/ansible/module_utils/facts/network/linux.py b/lib/ansible/module_utils/facts/network/linux.py
index a189f38..560cd25 100644
--- a/lib/ansible/module_utils/facts/network/linux.py
+++ b/lib/ansible/module_utils/facts/network/linux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import glob
import os
diff --git a/lib/ansible/module_utils/facts/network/netbsd.py b/lib/ansible/module_utils/facts/network/netbsd.py
index de8ceff..dde9e6c 100644
--- a/lib/ansible/module_utils/facts/network/netbsd.py
+++ b/lib/ansible/module_utils/facts/network/netbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork
diff --git a/lib/ansible/module_utils/facts/network/nvme.py b/lib/ansible/module_utils/facts/network/nvme.py
index 1d75956..7eb070d 100644
--- a/lib/ansible/module_utils/facts/network/nvme.py
+++ b/lib/ansible/module_utils/facts/network/nvme.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import sys
diff --git a/lib/ansible/module_utils/facts/network/openbsd.py b/lib/ansible/module_utils/facts/network/openbsd.py
index 9e11d82..691e624 100644
--- a/lib/ansible/module_utils/facts/network/openbsd.py
+++ b/lib/ansible/module_utils/facts/network/openbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork
diff --git a/lib/ansible/module_utils/facts/network/sunos.py b/lib/ansible/module_utils/facts/network/sunos.py
index adba14c..f2f064c 100644
--- a/lib/ansible/module_utils/facts/network/sunos.py
+++ b/lib/ansible/module_utils/facts/network/sunos.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/facts/other/facter.py b/lib/ansible/module_utils/facts/other/facter.py
index 0630652..ec1771e 100644
--- a/lib/ansible/module_utils/facts/other/facter.py
+++ b/lib/ansible/module_utils/facts/other/facter.py
@@ -1,8 +1,7 @@
# Copyright (c) 2023 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import json
diff --git a/lib/ansible/module_utils/facts/other/ohai.py b/lib/ansible/module_utils/facts/other/ohai.py
index 90c5539..75968ef 100644
--- a/lib/ansible/module_utils/facts/other/ohai.py
+++ b/lib/ansible/module_utils/facts/other/ohai.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import json
diff --git a/lib/ansible/module_utils/facts/packages.py b/lib/ansible/module_utils/facts/packages.py
index 53f74a1..21be56f 100644
--- a/lib/ansible/module_utils/facts/packages.py
+++ b/lib/ansible/module_utils/facts/packages.py
@@ -1,8 +1,7 @@
# (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
+from __future__ import annotations
from abc import ABCMeta, abstractmethod
diff --git a/lib/ansible/module_utils/facts/sysctl.py b/lib/ansible/module_utils/facts/sysctl.py
index d7bcc8a..1f94091 100644
--- a/lib/ansible/module_utils/facts/sysctl.py
+++ b/lib/ansible/module_utils/facts/sysctl.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/facts/system/apparmor.py b/lib/ansible/module_utils/facts/system/apparmor.py
index 3b702f9..ec29e88 100644
--- a/lib/ansible/module_utils/facts/system/apparmor.py
+++ b/lib/ansible/module_utils/facts/system/apparmor.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/system/caps.py b/lib/ansible/module_utils/facts/system/caps.py
index 3692f20..365a045 100644
--- a/lib/ansible/module_utils/facts/system/caps.py
+++ b/lib/ansible/module_utils/facts/system/caps.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/system/chroot.py b/lib/ansible/module_utils/facts/system/chroot.py
index 94138a0..bbf4b39 100644
--- a/lib/ansible/module_utils/facts/system/chroot.py
+++ b/lib/ansible/module_utils/facts/system/chroot.py
@@ -1,7 +1,6 @@
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/system/cmdline.py b/lib/ansible/module_utils/facts/system/cmdline.py
index 782186d..12376dc 100644
--- a/lib/ansible/module_utils/facts/system/cmdline.py
+++ b/lib/ansible/module_utils/facts/system/cmdline.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import shlex
diff --git a/lib/ansible/module_utils/facts/system/date_time.py b/lib/ansible/module_utils/facts/system/date_time.py
index 93af6dc..908d00a 100644
--- a/lib/ansible/module_utils/facts/system/date_time.py
+++ b/lib/ansible/module_utils/facts/system/date_time.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import datetime
import time
diff --git a/lib/ansible/module_utils/facts/system/distribution.py b/lib/ansible/module_utils/facts/system/distribution.py
index 6feece2..ee20fcb 100644
--- a/lib/ansible/module_utils/facts/system/distribution.py
+++ b/lib/ansible/module_utils/facts/system/distribution.py
@@ -3,8 +3,7 @@
# Copyright: (c) Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import platform
@@ -511,14 +510,14 @@ class Distribution(object):
# keep keys in sync with Conditionals page of docs
OS_FAMILY_MAP = {'RedHat': ['RedHat', 'RHEL', 'Fedora', 'CentOS', 'Scientific', 'SLC',
'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS',
- 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'Alibaba',
+ 'OEL', 'Amazon', 'Amzn', 'Virtuozzo', 'XenServer', 'Alibaba',
'EulerOS', 'openEuler', 'AlmaLinux', 'Rocky', 'TencentOS',
- 'EuroLinux', 'Kylin Linux Advanced Server'],
+ 'EuroLinux', 'Kylin Linux Advanced Server', 'MIRACLE'],
'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon',
'Linux Mint', 'SteamOS', 'Devuan', 'Kali', 'Cumulus Linux',
'Pop!_OS', 'Parrot', 'Pardus GNU/Linux', 'Uos', 'Deepin', 'OSMC'],
'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed',
- 'SLES_SAP', 'SUSE_LINUX', 'openSUSE Leap'],
+ 'SLES_SAP', 'SUSE_LINUX', 'openSUSE Leap', 'ALP-Dolomite'],
'Archlinux': ['Archlinux', 'Antergos', 'Manjaro'],
'Mandrake': ['Mandrake', 'Mandriva'],
'Solaris': ['Solaris', 'Nexenta', 'OmniOS', 'OpenIndiana', 'SmartOS'],
diff --git a/lib/ansible/module_utils/facts/system/dns.py b/lib/ansible/module_utils/facts/system/dns.py
index d913f4a..7ef69d1 100644
--- a/lib/ansible/module_utils/facts/system/dns.py
+++ b/lib/ansible/module_utils/facts/system/dns.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/system/env.py b/lib/ansible/module_utils/facts/system/env.py
index 605443f..4547924 100644
--- a/lib/ansible/module_utils/facts/system/env.py
+++ b/lib/ansible/module_utils/facts/system/env.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/system/fips.py b/lib/ansible/module_utils/facts/system/fips.py
index 7e56610..dbecd8f 100644
--- a/lib/ansible/module_utils/facts/system/fips.py
+++ b/lib/ansible/module_utils/facts/system/fips.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/system/loadavg.py b/lib/ansible/module_utils/facts/system/loadavg.py
index 8475f2a..37cb554 100644
--- a/lib/ansible/module_utils/facts/system/loadavg.py
+++ b/lib/ansible/module_utils/facts/system/loadavg.py
@@ -1,8 +1,7 @@
# (c) 2021 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/system/local.py b/lib/ansible/module_utils/facts/system/local.py
index 6681350..3d656f5 100644
--- a/lib/ansible/module_utils/facts/system/local.py
+++ b/lib/ansible/module_utils/facts/system/local.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import glob
import json
diff --git a/lib/ansible/module_utils/facts/system/lsb.py b/lib/ansible/module_utils/facts/system/lsb.py
index 2dc1433..5767536 100644
--- a/lib/ansible/module_utils/facts/system/lsb.py
+++ b/lib/ansible/module_utils/facts/system/lsb.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/system/pkg_mgr.py b/lib/ansible/module_utils/facts/system/pkg_mgr.py
index 14ad0a6..e9da186 100644
--- a/lib/ansible/module_utils/facts/system/pkg_mgr.py
+++ b/lib/ansible/module_utils/facts/system/pkg_mgr.py
@@ -2,8 +2,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import subprocess
@@ -16,11 +15,11 @@ from ansible.module_utils.facts.collector import BaseFactCollector
# package manager, put the preferred one last. If there is an
# ansible module, use that as the value for the 'name' key.
PKG_MGRS = [{'path': '/usr/bin/rpm-ostree', 'name': 'atomic_container'},
- {'path': '/usr/bin/yum', 'name': 'yum'},
# NOTE the `path` key for dnf/dnf5 is effectively discarded when matched for Red Hat OS family,
# special logic to infer the default `pkg_mgr` is used in `PkgMgrFactCollector._check_rh_versions()`
# leaving them here so a list of package modules can be constructed by iterating over `name` keys
+ {'path': '/usr/bin/yum', 'name': 'dnf'},
{'path': '/usr/bin/dnf-3', 'name': 'dnf'},
{'path': '/usr/bin/dnf5', 'name': 'dnf5'},
@@ -46,7 +45,6 @@ PKG_MGRS = [{'path': '/usr/bin/rpm-ostree', 'name': 'atomic_container'},
{'path': '/usr/bin/swupd', 'name': 'swupd'},
{'path': '/usr/sbin/sorcery', 'name': 'sorcery'},
{'path': '/usr/bin/installp', 'name': 'installp'},
- {'path': '/QOpenSys/pkgs/bin/yum', 'name': 'yum'},
]
@@ -70,39 +68,18 @@ class PkgMgrFactCollector(BaseFactCollector):
super(PkgMgrFactCollector, self).__init__(*args, **kwargs)
self._default_unknown_pkg_mgr = 'unknown'
- def _check_rh_versions(self, pkg_mgr_name, collected_facts):
+ def _check_rh_versions(self):
if os.path.exists('/run/ostree-booted'):
return "atomic_container"
- # Reset whatever was matched from PKG_MGRS, infer the default pkg_mgr below
- pkg_mgr_name = self._default_unknown_pkg_mgr
# Since /usr/bin/dnf and /usr/bin/microdnf can point to different versions of dnf in different distributions
# the only way to infer the default package manager is to look at the binary they are pointing to.
# /usr/bin/microdnf is likely used only in fedora minimal container so /usr/bin/dnf takes precedence
for bin_path in ('/usr/bin/dnf', '/usr/bin/microdnf'):
if os.path.exists(bin_path):
- pkg_mgr_name = 'dnf5' if os.path.realpath(bin_path) == '/usr/bin/dnf5' else 'dnf'
- break
-
- try:
- major_version = collected_facts['ansible_distribution_major_version']
- if collected_facts['ansible_distribution'] == 'Kylin Linux Advanced Server':
- major_version = major_version.lstrip('V')
- distro_major_ver = int(major_version)
- except ValueError:
- # a non integer magical future version
- return self._default_unknown_pkg_mgr
-
- if (
- (collected_facts['ansible_distribution'] == 'Fedora' and distro_major_ver < 23)
- or (collected_facts['ansible_distribution'] == 'Kylin Linux Advanced Server' and distro_major_ver < 10)
- or (collected_facts['ansible_distribution'] == 'Amazon' and distro_major_ver < 2022)
- or (collected_facts['ansible_distribution'] == 'TencentOS' and distro_major_ver < 3)
- or distro_major_ver < 8 # assume RHEL or a clone
- ) and any(pm for pm in PKG_MGRS if pm['name'] == 'yum' and os.path.exists(pm['path'])):
- pkg_mgr_name = 'yum'
+ return 'dnf5' if os.path.realpath(bin_path) == '/usr/bin/dnf5' else 'dnf'
- return pkg_mgr_name
+ return self._default_unknown_pkg_mgr
def _check_apt_flavor(self, pkg_mgr_name):
# Check if '/usr/bin/apt' is APT-RPM or an ordinary (dpkg-based) APT.
@@ -143,9 +120,9 @@ class PkgMgrFactCollector(BaseFactCollector):
# installed or available to the distro, the ansible_fact entry should be
# the default package manager officially supported by the distro.
if collected_facts['ansible_os_family'] == "RedHat":
- pkg_mgr_name = self._check_rh_versions(pkg_mgr_name, collected_facts)
+ pkg_mgr_name = self._check_rh_versions()
elif collected_facts['ansible_os_family'] == 'Debian' and pkg_mgr_name != 'apt':
- # It's possible to install yum, dnf, zypper, rpm, etc inside of
+ # It's possible to install dnf, zypper, rpm, etc inside of
# Debian. Doing so does not mean the system wants to use them.
pkg_mgr_name = 'apt'
elif collected_facts['ansible_os_family'] == 'Altlinux':
diff --git a/lib/ansible/module_utils/facts/system/platform.py b/lib/ansible/module_utils/facts/system/platform.py
index b947801..9481986 100644
--- a/lib/ansible/module_utils/facts/system/platform.py
+++ b/lib/ansible/module_utils/facts/system/platform.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
import socket
diff --git a/lib/ansible/module_utils/facts/system/python.py b/lib/ansible/module_utils/facts/system/python.py
index 50b66dd..0252c0c 100644
--- a/lib/ansible/module_utils/facts/system/python.py
+++ b/lib/ansible/module_utils/facts/system/python.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import sys
diff --git a/lib/ansible/module_utils/facts/system/selinux.py b/lib/ansible/module_utils/facts/system/selinux.py
index 5c6b012..c110f17 100644
--- a/lib/ansible/module_utils/facts/system/selinux.py
+++ b/lib/ansible/module_utils/facts/system/selinux.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/system/service_mgr.py b/lib/ansible/module_utils/facts/system/service_mgr.py
index 701def9..4dfa7e9 100644
--- a/lib/ansible/module_utils/facts/system/service_mgr.py
+++ b/lib/ansible/module_utils/facts/system/service_mgr.py
@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import platform
diff --git a/lib/ansible/module_utils/facts/system/ssh_pub_keys.py b/lib/ansible/module_utils/facts/system/ssh_pub_keys.py
index 85691c7..7214dea 100644
--- a/lib/ansible/module_utils/facts/system/ssh_pub_keys.py
+++ b/lib/ansible/module_utils/facts/system/ssh_pub_keys.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/system/user.py b/lib/ansible/module_utils/facts/system/user.py
index 2efa993..64b8fef 100644
--- a/lib/ansible/module_utils/facts/system/user.py
+++ b/lib/ansible/module_utils/facts/system/user.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import getpass
import os
diff --git a/lib/ansible/module_utils/facts/timeout.py b/lib/ansible/module_utils/facts/timeout.py
index ebb71cc..5fb749f 100644
--- a/lib/ansible/module_utils/facts/timeout.py
+++ b/lib/ansible/module_utils/facts/timeout.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import multiprocessing
import multiprocessing.pool as mp
diff --git a/lib/ansible/module_utils/facts/utils.py b/lib/ansible/module_utils/facts/utils.py
index a6027ab..f7f6f19 100644
--- a/lib/ansible/module_utils/facts/utils.py
+++ b/lib/ansible/module_utils/facts/utils.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import fcntl
import os
diff --git a/lib/ansible/module_utils/facts/virtual/base.py b/lib/ansible/module_utils/facts/virtual/base.py
index 67b59a5..943ce40 100644
--- a/lib/ansible/module_utils/facts/virtual/base.py
+++ b/lib/ansible/module_utils/facts/virtual/base.py
@@ -16,8 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import ansible.module_utils.compat.typing as t
diff --git a/lib/ansible/module_utils/facts/virtual/dragonfly.py b/lib/ansible/module_utils/facts/virtual/dragonfly.py
index b176f8b..8e1aa0d 100644
--- a/lib/ansible/module_utils/facts/virtual/dragonfly.py
+++ b/lib/ansible/module_utils/facts/virtual/dragonfly.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.facts.virtual.freebsd import FreeBSDVirtual, VirtualCollector
diff --git a/lib/ansible/module_utils/facts/virtual/freebsd.py b/lib/ansible/module_utils/facts/virtual/freebsd.py
index 7062d01..819aa02 100644
--- a/lib/ansible/module_utils/facts/virtual/freebsd.py
+++ b/lib/ansible/module_utils/facts/virtual/freebsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/virtual/hpux.py b/lib/ansible/module_utils/facts/virtual/hpux.py
index 1057482..5164aab 100644
--- a/lib/ansible/module_utils/facts/virtual/hpux.py
+++ b/lib/ansible/module_utils/facts/virtual/hpux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
import re
diff --git a/lib/ansible/module_utils/facts/virtual/linux.py b/lib/ansible/module_utils/facts/virtual/linux.py
index c368245..57b047b 100644
--- a/lib/ansible/module_utils/facts/virtual/linux.py
+++ b/lib/ansible/module_utils/facts/virtual/linux.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import glob
import os
diff --git a/lib/ansible/module_utils/facts/virtual/netbsd.py b/lib/ansible/module_utils/facts/virtual/netbsd.py
index b4ef14e..1689ac3 100644
--- a/lib/ansible/module_utils/facts/virtual/netbsd.py
+++ b/lib/ansible/module_utils/facts/virtual/netbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/virtual/openbsd.py b/lib/ansible/module_utils/facts/virtual/openbsd.py
index c449028..5c12df8 100644
--- a/lib/ansible/module_utils/facts/virtual/openbsd.py
+++ b/lib/ansible/module_utils/facts/virtual/openbsd.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/facts/virtual/sunos.py b/lib/ansible/module_utils/facts/virtual/sunos.py
index 1e92677..7a595f7 100644
--- a/lib/ansible/module_utils/facts/virtual/sunos.py
+++ b/lib/ansible/module_utils/facts/virtual/sunos.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import os
diff --git a/lib/ansible/module_utils/facts/virtual/sysctl.py b/lib/ansible/module_utils/facts/virtual/sysctl.py
index 1c7b2b3..649f335 100644
--- a/lib/ansible/module_utils/facts/virtual/sysctl.py
+++ b/lib/ansible/module_utils/facts/virtual/sysctl.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import re
diff --git a/lib/ansible/module_utils/json_utils.py b/lib/ansible/module_utils/json_utils.py
index 1ec971c..c6d4c76 100644
--- a/lib/ansible/module_utils/json_utils.py
+++ b/lib/ansible/module_utils/json_utils.py
@@ -24,8 +24,7 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import json # pylint: disable=unused-import
diff --git a/lib/ansible/module_utils/parsing/convert_bool.py b/lib/ansible/module_utils/parsing/convert_bool.py
index fb331d8..3367b2a 100644
--- a/lib/ansible/module_utils/parsing/convert_bool.py
+++ b/lib/ansible/module_utils/parsing/convert_bool.py
@@ -1,8 +1,7 @@
# Copyright: 2017, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause )
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
from ansible.module_utils.six import binary_type, text_type
from ansible.module_utils.common.text.converters import to_text
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1
index 4aea98b..a716c3a 100644
--- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1
@@ -372,8 +372,11 @@ Function Get-PendingRebootStatus {
<#
.SYNOPSIS
Check if reboot is required, if so notify CA.
- Function returns true if computer has a pending reboot
-#>
+ Function returns true if computer has a pending reboot.
+
+ People should not be using this function, it is kept
+ just for backwards compatibility.
+ #>
$featureData = Invoke-CimMethod -EA Ignore -Name GetServerFeature -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks
$regData = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" "PendingFileRenameOperations" -EA Ignore
$CBSRebootStatus = Get-ChildItem "HKLM:\\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" -ErrorAction SilentlyContinue |
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1
index b59ba72..29e5be1 100644
--- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1
@@ -355,7 +355,7 @@ Function Invoke-WithWebRequest {
.PARAMETER Module
The Ansible.Basic module to set the return values for. This will set the following return values;
elapsed - The total time, in seconds, that it took to send the web request and process the response
- msg - The human readable description of the response status code
+ msg - The human-readable description of the response status code
status_code - An int that is the response status code
.PARAMETER Request
diff --git a/lib/ansible/module_utils/pycompat24.py b/lib/ansible/module_utils/pycompat24.py
index d57f968..27d6148 100644
--- a/lib/ansible/module_utils/pycompat24.py
+++ b/lib/ansible/module_utils/pycompat24.py
@@ -26,11 +26,12 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import sys
+from ansible.module_utils.common.warnings import deprecate
+
def get_exception():
"""Get the current exception.
@@ -44,10 +45,29 @@ def get_exception():
e = get_exception()
"""
+ deprecate(
+ msg='The `ansible.module_utils.pycompat24.get_exception` '
+ 'function is deprecated.',
+ version='2.19',
+ )
return sys.exc_info()[1]
-from ast import literal_eval
+def __getattr__(importable_name):
+ """Inject import-time deprecation warning for ``literal_eval()``."""
+ if importable_name == 'literal_eval':
+ deprecate(
+ msg=f'The `ansible.module_utils.pycompat24.'
+ f'{importable_name}` function is deprecated.',
+ version='2.19',
+ )
+ from ast import literal_eval
+ return literal_eval
+
+ raise AttributeError(
+ f'cannot import name {importable_name !r} '
+ f'has no attribute ({__file__ !s})',
+ )
-__all__ = ('get_exception', 'literal_eval')
+__all__ = ('get_exception', 'literal_eval') # pylint: disable=undefined-all-variable
diff --git a/lib/ansible/module_utils/service.py b/lib/ansible/module_utils/service.py
index e79f40e..3910ea0 100644
--- a/lib/ansible/module_utils/service.py
+++ b/lib/ansible/module_utils/service.py
@@ -26,8 +26,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
import glob
import os
@@ -111,8 +110,8 @@ def fail_if_missing(module, found, service, msg=''):
This function will return an error or exit gracefully depending on check mode status
and if the service is missing or not.
- :arg module: is an AnsibleModule object, used for it's utility methods
- :arg found: boolean indicating if services was found or not
+ :arg module: is an AnsibleModule object, used for it's utility methods
+ :arg found: boolean indicating if services were found or not
:arg service: name of service
:kw msg: extra info to append to error/success msg when missing
'''
@@ -166,7 +165,7 @@ def daemonize(module, cmd):
'''
Execute a command while detaching as a daemon, returns rc, stdout, and stderr.
- :arg module: is an AnsibleModule object, used for it's utility methods
+ :arg module: is an AnsibleModule object, used for it's utility methods
:arg cmd: is a list or string representing the command and options to run
This is complex because daemonization is hard for people.
@@ -275,3 +274,30 @@ def check_ps(module, pattern):
if pattern in line:
return True
return False
+
+
+def is_systemd_managed(module):
+ """
+ Find out if the machine supports systemd or not
+ :arg module: is an AnsibleModule object, used for it's utility methods
+
+ Returns True if the system supports systemd, False if not.
+ """
+ # tools must be installed
+ if module.get_bin_path('systemctl'):
+ # This should show if systemd is the boot init system, if checking init failed to mark as systemd
+ # these mirror systemd's own sd_boot test http://www.freedesktop.org/software/systemd/man/sd_booted.html
+ for canary in ["/run/systemd/system/", "/dev/.run/systemd/", "/dev/.systemd/"]:
+ if os.path.exists(canary):
+ return True
+
+ # If all else fails, check if init is the systemd command, using comm as cmdline could be symlink
+ try:
+ with open('/proc/1/comm', 'r') as init_proc:
+ init = init_proc.readline().strip()
+ return init == 'systemd'
+ except IOError:
+ # If comm doesn't exist, old kernel, no systemd
+ return False
+
+ return False
diff --git a/lib/ansible/module_utils/six/__init__.py b/lib/ansible/module_utils/six/__init__.py
index f2d41c8..4e74af7 100644
--- a/lib/ansible/module_utils/six/__init__.py
+++ b/lib/ansible/module_utils/six/__init__.py
@@ -25,7 +25,7 @@
"""Utilities for writing code that runs on Python 2 and 3"""
-from __future__ import absolute_import
+from __future__ import annotations
import functools
import itertools
diff --git a/lib/ansible/module_utils/splitter.py b/lib/ansible/module_utils/splitter.py
index c170b1c..7bddd32 100644
--- a/lib/ansible/module_utils/splitter.py
+++ b/lib/ansible/module_utils/splitter.py
@@ -26,8 +26,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
def _get_quote_state(token, quote_char):
diff --git a/lib/ansible/module_utils/urls.py b/lib/ansible/module_utils/urls.py
index 42ef55b..c4c8e3a 100644
--- a/lib/ansible/module_utils/urls.py
+++ b/lib/ansible/module_utils/urls.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
@@ -6,56 +7,51 @@
#
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com>, 2015
+# Copyright: Contributors to the Ansible project
#
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
-#
-# The match_hostname function and supporting code is under the terms and
-# conditions of the Python Software Foundation License. They were taken from
-# the Python3 standard library and adapted for use in Python2. See comments in the
-# source for which code precisely is under this License.
-#
-# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0)
'''
-The **urls** utils module offers a replacement for the urllib2 python library.
+The **urls** utils module offers a replacement for the urllib python library.
-urllib2 is the python stdlib way to retrieve files from the Internet but it
+urllib is the python stdlib way to retrieve files from the Internet but it
lacks some security features (around verifying SSL certificates) that users
should care about in most situations. Using the functions in this module corrects
-deficiencies in the urllib2 module wherever possible.
+deficiencies in the urllib module wherever possible.
There are also third-party libraries (for instance, requests) which can be used
-to replace urllib2 with a more secure library. However, all third party libraries
+to replace urllib with a more secure library. However, all third party libraries
require that the library be installed on the managed machine. That is an extra step
for users making use of a module. If possible, avoid third party libraries by using
this code instead.
'''
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
-import atexit
import base64
+import email.mime.application
import email.mime.multipart
import email.mime.nonmultipart
-import email.mime.application
import email.parser
+import email.policy
import email.utils
-import functools
-import io
+import http.client
import mimetypes
import netrc
import os
import platform
import re
import socket
-import sys
import tempfile
import traceback
import types # pylint: disable=unused-import
-
+import urllib.error
+import urllib.request
from contextlib import contextmanager
+from http import cookiejar
+from urllib.parse import unquote, urlparse, urlunparse
+from urllib.request import BaseHandler
try:
import gzip
@@ -68,123 +64,16 @@ except ImportError:
else:
GzipFile = gzip.GzipFile # type: ignore[assignment,misc]
-try:
- import email.policy
-except ImportError:
- # Py2
- import email.generator
-
-try:
- import httplib
-except ImportError:
- # Python 3
- import http.client as httplib # type: ignore[no-redef]
-
-import ansible.module_utils.compat.typing as t
-import ansible.module_utils.six.moves.http_cookiejar as cookiejar
-import ansible.module_utils.six.moves.urllib.error as urllib_error
-
+from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.collections import Mapping, is_sequence
-from ansible.module_utils.six import PY2, PY3, string_types
-from ansible.module_utils.six.moves import cStringIO
-from ansible.module_utils.basic import get_distribution, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
try:
- # python3
- import urllib.request as urllib_request
- from urllib.request import AbstractHTTPHandler, BaseHandler
-except ImportError:
- # python2
- import urllib2 as urllib_request # type: ignore[no-redef]
- from urllib2 import AbstractHTTPHandler, BaseHandler # type: ignore[no-redef]
-
-urllib_request.HTTPRedirectHandler.http_error_308 = urllib_request.HTTPRedirectHandler.http_error_307 # type: ignore[attr-defined,assignment]
-
-try:
- from ansible.module_utils.six.moves.urllib.parse import urlparse, urlunparse, unquote
- HAS_URLPARSE = True
-except Exception:
- HAS_URLPARSE = False
-
-try:
import ssl
HAS_SSL = True
except Exception:
HAS_SSL = False
-try:
- # SNI Handling needs python2.7.9's SSLContext
- from ssl import create_default_context, SSLContext # pylint: disable=unused-import
- HAS_SSLCONTEXT = True
-except ImportError:
- HAS_SSLCONTEXT = False
-
-# SNI Handling for python < 2.7.9 with urllib3 support
-HAS_URLLIB3_PYOPENSSLCONTEXT = False
-HAS_URLLIB3_SSL_WRAP_SOCKET = False
-if not HAS_SSLCONTEXT:
- try:
- # urllib3>=1.15
- try:
- from urllib3.contrib.pyopenssl import PyOpenSSLContext
- except Exception:
- from requests.packages.urllib3.contrib.pyopenssl import PyOpenSSLContext # type: ignore[no-redef]
- HAS_URLLIB3_PYOPENSSLCONTEXT = True
- except Exception:
- # urllib3<1.15,>=1.6
- try:
- try:
- from urllib3.contrib.pyopenssl import ssl_wrap_socket # type: ignore[attr-defined]
- except Exception:
- from requests.packages.urllib3.contrib.pyopenssl import ssl_wrap_socket
- HAS_URLLIB3_SSL_WRAP_SOCKET = True
- except Exception:
- pass
-
-# Select a protocol that includes all secure tls protocols
-# Exclude insecure ssl protocols if possible
-
-if HAS_SSL:
- # If we can't find extra tls methods, ssl.PROTOCOL_TLSv1 is sufficient
- PROTOCOL = ssl.PROTOCOL_TLSv1
-if not HAS_SSLCONTEXT and HAS_SSL:
- try:
- import ctypes
- import ctypes.util
- except ImportError:
- # python 2.4 (likely rhel5 which doesn't have tls1.1 support in its openssl)
- pass
- else:
- libssl_name = ctypes.util.find_library('ssl')
- libssl = ctypes.CDLL(libssl_name)
- for method in ('TLSv1_1_method', 'TLSv1_2_method'):
- try:
- libssl[method] # pylint: disable=pointless-statement
- # Found something - we'll let openssl autonegotiate and hope
- # the server has disabled sslv2 and 3. best we can do.
- PROTOCOL = ssl.PROTOCOL_SSLv23
- break
- except AttributeError:
- pass
- del libssl
-
-
-# The following makes it easier for us to script updates of the bundled backports.ssl_match_hostname
-# The bundled backports.ssl_match_hostname should really be moved into its own file for processing
-_BUNDLED_METADATA = {"pypi_name": "backports.ssl_match_hostname", "version": "3.7.0.1"}
-
-LOADED_VERIFY_LOCATIONS = set() # type: t.Set[str]
-
-HAS_MATCH_HOSTNAME = True
-try:
- from ssl import match_hostname, CertificateError
-except ImportError:
- try:
- from backports.ssl_match_hostname import match_hostname, CertificateError # type: ignore[assignment]
- except ImportError:
- HAS_MATCH_HOSTNAME = False
-
HAS_CRYPTOGRAPHY = True
try:
from cryptography import x509
@@ -226,7 +115,7 @@ try:
if self._context:
return
- parsed = generic_urlparse(urlparse(req.get_full_url()))
+ parsed = urlparse(req.get_full_url())
auth_header = self.get_auth_value(headers)
if not auth_header:
@@ -259,7 +148,7 @@ try:
cbt = gssapi.raw.ChannelBindings(application_data=b"tls-server-end-point:" + cert_hash)
# TODO: We could add another option that is set to include the port in the SPN if desired in the future.
- target = gssapi.Name("HTTP@%s" % parsed['hostname'], gssapi.NameType.hostbased_service)
+ target = gssapi.Name("HTTP@%s" % parsed.hostname, gssapi.NameType.hostbased_service)
self._context = gssapi.SecurityContext(usage="initiate", name=target, creds=cred, channel_bindings=cbt)
resp = None
@@ -284,213 +173,9 @@ except ImportError:
GSSAPI_IMP_ERR = traceback.format_exc()
HTTPGSSAPIAuthHandler = None # type: types.ModuleType | None # type: ignore[no-redef]
-if not HAS_MATCH_HOSTNAME:
- # The following block of code is under the terms and conditions of the
- # Python Software Foundation License
-
- # The match_hostname() function from Python 3.4, essential when using SSL.
-
- try:
- # Divergence: Python-3.7+'s _ssl has this exception type but older Pythons do not
- from _ssl import SSLCertVerificationError
- CertificateError = SSLCertVerificationError # type: ignore[misc]
- except ImportError:
- class CertificateError(ValueError): # type: ignore[no-redef]
- pass
-
- def _dnsname_match(dn, hostname):
- """Matching according to RFC 6125, section 6.4.3
-
- - Hostnames are compared lower case.
- - For IDNA, both dn and hostname must be encoded as IDN A-label (ACE).
- - Partial wildcards like 'www*.example.org', multiple wildcards, sole
- wildcard or wildcards in labels other then the left-most label are not
- supported and a CertificateError is raised.
- - A wildcard must match at least one character.
- """
- if not dn:
- return False
-
- wildcards = dn.count('*')
- # speed up common case w/o wildcards
- if not wildcards:
- return dn.lower() == hostname.lower()
-
- if wildcards > 1:
- # Divergence .format() to percent formatting for Python < 2.6
- raise CertificateError(
- "too many wildcards in certificate DNS name: %s" % repr(dn))
-
- dn_leftmost, sep, dn_remainder = dn.partition('.')
-
- if '*' in dn_remainder:
- # Only match wildcard in leftmost segment.
- # Divergence .format() to percent formatting for Python < 2.6
- raise CertificateError(
- "wildcard can only be present in the leftmost label: "
- "%s." % repr(dn))
-
- if not sep:
- # no right side
- # Divergence .format() to percent formatting for Python < 2.6
- raise CertificateError(
- "sole wildcard without additional labels are not support: "
- "%s." % repr(dn))
-
- if dn_leftmost != '*':
- # no partial wildcard matching
- # Divergence .format() to percent formatting for Python < 2.6
- raise CertificateError(
- "partial wildcards in leftmost label are not supported: "
- "%s." % repr(dn))
-
- hostname_leftmost, sep, hostname_remainder = hostname.partition('.')
- if not hostname_leftmost or not sep:
- # wildcard must match at least one char
- return False
- return dn_remainder.lower() == hostname_remainder.lower()
-
- def _inet_paton(ipname):
- """Try to convert an IP address to packed binary form
-
- Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6
- support.
- """
- # inet_aton() also accepts strings like '1'
- # Divergence: We make sure we have native string type for all python versions
- try:
- b_ipname = to_bytes(ipname, errors='strict')
- except UnicodeError:
- raise ValueError("%s must be an all-ascii string." % repr(ipname))
-
- # Set ipname in native string format
- if sys.version_info < (3,):
- n_ipname = b_ipname
- else:
- n_ipname = ipname
-
- if n_ipname.count('.') == 3:
- try:
- return socket.inet_aton(n_ipname)
- # Divergence: OSError on late python3. socket.error earlier.
- # Null bytes generate ValueError on python3(we want to raise
- # ValueError anyway), TypeError # earlier
- except (OSError, socket.error, TypeError):
- pass
-
- try:
- return socket.inet_pton(socket.AF_INET6, n_ipname)
- # Divergence: OSError on late python3. socket.error earlier.
- # Null bytes generate ValueError on python3(we want to raise
- # ValueError anyway), TypeError # earlier
- except (OSError, socket.error, TypeError):
- # Divergence .format() to percent formatting for Python < 2.6
- raise ValueError("%s is neither an IPv4 nor an IP6 "
- "address." % repr(ipname))
- except AttributeError:
- # AF_INET6 not available
- pass
-
- # Divergence .format() to percent formatting for Python < 2.6
- raise ValueError("%s is not an IPv4 address." % repr(ipname))
-
- def _ipaddress_match(ipname, host_ip):
- """Exact matching of IP addresses.
- RFC 6125 explicitly doesn't define an algorithm for this
- (section 1.7.2 - "Out of Scope").
- """
- # OpenSSL may add a trailing newline to a subjectAltName's IP address
- ip = _inet_paton(ipname.rstrip())
- return ip == host_ip
-
- def match_hostname(cert, hostname): # type: ignore[misc]
- """Verify that *cert* (in decoded format as returned by
- SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
- rules are followed.
-
- The function matches IP addresses rather than dNSNames if hostname is a
- valid ipaddress string. IPv4 addresses are supported on all platforms.
- IPv6 addresses are supported on platforms with IPv6 support (AF_INET6
- and inet_pton).
-
- CertificateError is raised on failure. On success, the function
- returns nothing.
- """
- if not cert:
- raise ValueError("empty or no certificate, match_hostname needs a "
- "SSL socket or SSL context with either "
- "CERT_OPTIONAL or CERT_REQUIRED")
- try:
- # Divergence: Deal with hostname as bytes
- host_ip = _inet_paton(to_text(hostname, errors='strict'))
- except UnicodeError:
- # Divergence: Deal with hostname as byte strings.
- # IP addresses should be all ascii, so we consider it not
- # an IP address if this fails
- host_ip = None
- except ValueError:
- # Not an IP address (common case)
- host_ip = None
- dnsnames = []
- san = cert.get('subjectAltName', ())
- for key, value in san:
- if key == 'DNS':
- if host_ip is None and _dnsname_match(value, hostname):
- return
- dnsnames.append(value)
- elif key == 'IP Address':
- if host_ip is not None and _ipaddress_match(value, host_ip):
- return
- dnsnames.append(value)
- if not dnsnames:
- # The subject is only checked when there is no dNSName entry
- # in subjectAltName
- for sub in cert.get('subject', ()):
- for key, value in sub:
- # XXX according to RFC 2818, the most specific Common Name
- # must be used.
- if key == 'commonName':
- if _dnsname_match(value, hostname):
- return
- dnsnames.append(value)
- if len(dnsnames) > 1:
- raise CertificateError("hostname %r doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames))))
- elif len(dnsnames) == 1:
- raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0]))
- else:
- raise CertificateError("no appropriate commonName or subjectAltName fields were found")
-
- # End of Python Software Foundation Licensed code
-
- HAS_MATCH_HOSTNAME = True
-
-
-# This is a dummy cacert provided for macOS since you need at least 1
-# ca cert, regardless of validity, for Python on macOS to use the
-# keychain functionality in OpenSSL for validating SSL certificates.
-# See: http://mercurial.selenic.com/wiki/CACertificates#Mac_OS_X_10.6_and_higher
-b_DUMMY_CA_CERT = b"""-----BEGIN CERTIFICATE-----
-MIICvDCCAiWgAwIBAgIJAO8E12S7/qEpMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
-BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEPMA0GA1UEBxMGRHVyaGFt
-MRAwDgYDVQQKEwdBbnNpYmxlMB4XDTE0MDMxODIyMDAyMloXDTI0MDMxNTIyMDAy
-MlowSTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYD
-VQQHEwZEdXJoYW0xEDAOBgNVBAoTB0Fuc2libGUwgZ8wDQYJKoZIhvcNAQEBBQAD
-gY0AMIGJAoGBANtvpPq3IlNlRbCHhZAcP6WCzhc5RbsDqyh1zrkmLi0GwcQ3z/r9
-gaWfQBYhHpobK2Tiq11TfraHeNB3/VfNImjZcGpN8Fl3MWwu7LfVkJy3gNNnxkA1
-4Go0/LmIvRFHhbzgfuo9NFgjPmmab9eqXJceqZIlz2C8xA7EeG7ku0+vAgMBAAGj
-gaswgagwHQYDVR0OBBYEFPnN1nPRqNDXGlCqCvdZchRNi/FaMHkGA1UdIwRyMHCA
-FPnN1nPRqNDXGlCqCvdZchRNi/FaoU2kSzBJMQswCQYDVQQGEwJVUzEXMBUGA1UE
-CBMOTm9ydGggQ2Fyb2xpbmExDzANBgNVBAcTBkR1cmhhbTEQMA4GA1UEChMHQW5z
-aWJsZYIJAO8E12S7/qEpMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
-MUB80IR6knq9K/tY+hvPsZer6eFMzO3JGkRFBh2kn6JdMDnhYGX7AXVHGflrwNQH
-qFy+aenWXsC0ZvrikFxbQnX8GVtDADtVznxOi7XzFw7JOxdsVrpXgSN0eh0aMzvV
-zKPZsZ2miVGclicJHzm5q080b1p/sZtuKIEZk6vZqEg=
------END CERTIFICATE-----
-"""
-
-b_PEM_CERT_RE = re.compile(
- br'^-----BEGIN CERTIFICATE-----\n.+?-----END CERTIFICATE-----$',
+PEM_CERT_RE = re.compile(
+ r'^-----BEGIN CERTIFICATE-----\n.+?-----END CERTIFICATE-----$',
flags=re.M | re.S
)
@@ -510,142 +195,81 @@ class ProxyError(ConnectionError):
class SSLValidationError(ConnectionError):
- """Failure to connect due to SSL validation failing"""
+ """Failure to connect due to SSL validation failing
+
+ No longer used, but kept for backwards compatibility
+ """
pass
class NoSSLError(SSLValidationError):
- """Needed to connect to an HTTPS url but no ssl library available to verify the certificate"""
+ """Needed to connect to an HTTPS url but no ssl library available to verify the certificate
+
+ No longer used, but kept for backwards compatibility
+ """
pass
class MissingModuleError(Exception):
"""Failed to import 3rd party module required by the caller"""
def __init__(self, message, import_traceback, module=None):
- super(MissingModuleError, self).__init__(message)
+ super().__init__(message)
self.import_traceback = import_traceback
self.module = module
-# Some environments (Google Compute Engine's CoreOS deploys) do not compile
-# against openssl and thus do not have any HTTPS support.
-CustomHTTPSConnection = None
-CustomHTTPSHandler = None
-HTTPSClientAuthHandler = None
+UnixHTTPSHandler = None
UnixHTTPSConnection = None
-if hasattr(httplib, 'HTTPSConnection') and hasattr(urllib_request, 'HTTPSHandler'):
- class CustomHTTPSConnection(httplib.HTTPSConnection): # type: ignore[no-redef]
- def __init__(self, client_cert=None, client_key=None, *args, **kwargs):
- httplib.HTTPSConnection.__init__(self, *args, **kwargs)
- self.context = None
- if HAS_SSLCONTEXT:
- self.context = self._context
- elif HAS_URLLIB3_PYOPENSSLCONTEXT:
- self.context = self._context = PyOpenSSLContext(PROTOCOL)
-
- self._client_cert = client_cert
- self._client_key = client_key
- if self.context and self._client_cert:
- self.context.load_cert_chain(self._client_cert, self._client_key)
-
- def connect(self):
- "Connect to a host on a given (SSL) port."
-
- if hasattr(self, 'source_address'):
- sock = socket.create_connection((self.host, self.port), self.timeout, self.source_address)
- else:
- sock = socket.create_connection((self.host, self.port), self.timeout)
-
- server_hostname = self.host
- # Note: self._tunnel_host is not available on py < 2.6 but this code
- # isn't used on py < 2.6 (lack of create_connection)
- if self._tunnel_host:
- self.sock = sock
- self._tunnel()
- server_hostname = self._tunnel_host
-
- if HAS_SSLCONTEXT or HAS_URLLIB3_PYOPENSSLCONTEXT:
- self.sock = self.context.wrap_socket(sock, server_hostname=server_hostname)
- elif HAS_URLLIB3_SSL_WRAP_SOCKET:
- self.sock = ssl_wrap_socket(sock, keyfile=self._client_key, cert_reqs=ssl.CERT_NONE, # pylint: disable=used-before-assignment
- certfile=self._client_cert, ssl_version=PROTOCOL, server_hostname=server_hostname)
- else:
- self.sock = ssl.wrap_socket(sock, keyfile=self._client_key, certfile=self._client_cert, ssl_version=PROTOCOL)
-
- class CustomHTTPSHandler(urllib_request.HTTPSHandler): # type: ignore[no-redef]
-
- def https_open(self, req):
- kwargs = {}
- if HAS_SSLCONTEXT:
- kwargs['context'] = self._context
- return self.do_open(
- functools.partial(
- CustomHTTPSConnection,
- **kwargs
- ),
- req
- )
-
- https_request = AbstractHTTPHandler.do_request_
-
- class HTTPSClientAuthHandler(urllib_request.HTTPSHandler): # type: ignore[no-redef]
- '''Handles client authentication via cert/key
-
- This is a fairly lightweight extension on HTTPSHandler, and can be used
- in place of HTTPSHandler
- '''
-
- def __init__(self, client_cert=None, client_key=None, unix_socket=None, **kwargs):
- urllib_request.HTTPSHandler.__init__(self, **kwargs)
- self.client_cert = client_cert
- self.client_key = client_key
- self._unix_socket = unix_socket
-
- def https_open(self, req):
- return self.do_open(self._build_https_connection, req)
-
- def _build_https_connection(self, host, **kwargs):
- try:
- kwargs['context'] = self._context
- except AttributeError:
- pass
- if self._unix_socket:
- return UnixHTTPSConnection(self._unix_socket)(host, **kwargs)
- if not HAS_SSLCONTEXT:
- return CustomHTTPSConnection(host, client_cert=self.client_cert, client_key=self.client_key, **kwargs)
- return httplib.HTTPSConnection(host, **kwargs)
-
+if HAS_SSL:
@contextmanager
def unix_socket_patch_httpconnection_connect():
- '''Monkey patch ``httplib.HTTPConnection.connect`` to be ``UnixHTTPConnection.connect``
+ '''Monkey patch ``http.client.HTTPConnection.connect`` to be ``UnixHTTPConnection.connect``
so that when calling ``super(UnixHTTPSConnection, self).connect()`` we get the
correct behavior of creating self.sock for the unix socket
'''
- _connect = httplib.HTTPConnection.connect
- httplib.HTTPConnection.connect = UnixHTTPConnection.connect
+ _connect = http.client.HTTPConnection.connect
+ http.client.HTTPConnection.connect = UnixHTTPConnection.connect
yield
- httplib.HTTPConnection.connect = _connect
+ http.client.HTTPConnection.connect = _connect
- class UnixHTTPSConnection(httplib.HTTPSConnection): # type: ignore[no-redef]
+ class UnixHTTPSConnection(http.client.HTTPSConnection): # type: ignore[no-redef]
def __init__(self, unix_socket):
self._unix_socket = unix_socket
def connect(self):
# This method exists simply to ensure we monkeypatch
- # httplib.HTTPConnection.connect to call UnixHTTPConnection.connect
+ # http.client.HTTPConnection.connect to call UnixHTTPConnection.connect
with unix_socket_patch_httpconnection_connect():
# Disable pylint check for the super() call. It complains about UnixHTTPSConnection
# being a NoneType because of the initial definition above, but it won't actually
# be a NoneType when this code runs
- # pylint: disable=bad-super-call
- super(UnixHTTPSConnection, self).connect()
+ super().connect()
def __call__(self, *args, **kwargs):
- httplib.HTTPSConnection.__init__(self, *args, **kwargs)
+ super().__init__(*args, **kwargs)
return self
+ class UnixHTTPSHandler(urllib.request.HTTPSHandler): # type: ignore[no-redef]
+ def __init__(self, unix_socket, **kwargs):
+ super().__init__(**kwargs)
+ self._unix_socket = unix_socket
+
+ def https_open(self, req):
+ kwargs = {}
+ try:
+ # deprecated: description='deprecated check_hostname' python_version='3.12'
+ kwargs['check_hostname'] = self._check_hostname
+ except AttributeError:
+ pass
+ return self.do_open(
+ UnixHTTPSConnection(self._unix_socket),
+ req,
+ context=self._context,
+ **kwargs
+ )
+
-class UnixHTTPConnection(httplib.HTTPConnection):
+class UnixHTTPConnection(http.client.HTTPConnection):
'''Handles http requests to a unix socket file'''
def __init__(self, unix_socket):
@@ -661,15 +285,15 @@ class UnixHTTPConnection(httplib.HTTPConnection):
self.sock.settimeout(self.timeout)
def __call__(self, *args, **kwargs):
- httplib.HTTPConnection.__init__(self, *args, **kwargs)
+ super().__init__(*args, **kwargs)
return self
-class UnixHTTPHandler(urllib_request.HTTPHandler):
+class UnixHTTPHandler(urllib.request.HTTPHandler):
'''Handler for Unix urls'''
def __init__(self, unix_socket, **kwargs):
- urllib_request.HTTPHandler.__init__(self, **kwargs)
+ super().__init__(**kwargs)
self._unix_socket = unix_socket
def http_open(self, req):
@@ -681,7 +305,7 @@ class ParseResultDottedDict(dict):
A dict that acts similarly to the ParseResult named tuple from urllib
'''
def __init__(self, *args, **kwargs):
- super(ParseResultDottedDict, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.__dict__ = self
def as_list(self):
@@ -696,93 +320,25 @@ def generic_urlparse(parts):
Returns a dictionary of url parts as parsed by urlparse,
but accounts for the fact that older versions of that
library do not support named attributes (ie. .netloc)
- '''
- generic_parts = ParseResultDottedDict()
- if hasattr(parts, 'netloc'):
- # urlparse is newer, just read the fields straight
- # from the parts object
- generic_parts['scheme'] = parts.scheme
- generic_parts['netloc'] = parts.netloc
- generic_parts['path'] = parts.path
- generic_parts['params'] = parts.params
- generic_parts['query'] = parts.query
- generic_parts['fragment'] = parts.fragment
- generic_parts['username'] = parts.username
- generic_parts['password'] = parts.password
- hostname = parts.hostname
- if hostname and hostname[0] == '[' and '[' in parts.netloc and ']' in parts.netloc:
- # Py2.6 doesn't parse IPv6 addresses correctly
- hostname = parts.netloc.split(']')[0][1:].lower()
- generic_parts['hostname'] = hostname
-
- try:
- port = parts.port
- except ValueError:
- # Py2.6 doesn't parse IPv6 addresses correctly
- netloc = parts.netloc.split('@')[-1].split(']')[-1]
- if ':' in netloc:
- port = netloc.split(':')[1]
- if port:
- port = int(port)
- else:
- port = None
- generic_parts['port'] = port
- else:
- # we have to use indexes, and then parse out
- # the other parts not supported by indexing
- generic_parts['scheme'] = parts[0]
- generic_parts['netloc'] = parts[1]
- generic_parts['path'] = parts[2]
- generic_parts['params'] = parts[3]
- generic_parts['query'] = parts[4]
- generic_parts['fragment'] = parts[5]
- # get the username, password, etc.
- try:
- netloc_re = re.compile(r'^((?:\w)+(?::(?:\w)+)?@)?([A-Za-z0-9.-]+)(:\d+)?$')
- match = netloc_re.match(parts[1])
- auth = match.group(1)
- hostname = match.group(2)
- port = match.group(3)
- if port:
- # the capture group for the port will include the ':',
- # so remove it and convert the port to an integer
- port = int(port[1:])
- if auth:
- # the capture group above includes the @, so remove it
- # and then split it up based on the first ':' found
- auth = auth[:-1]
- username, password = auth.split(':', 1)
- else:
- username = password = None
- generic_parts['username'] = username
- generic_parts['password'] = password
- generic_parts['hostname'] = hostname
- generic_parts['port'] = port
- except Exception:
- generic_parts['username'] = None
- generic_parts['password'] = None
- generic_parts['hostname'] = parts[1]
- generic_parts['port'] = None
- return generic_parts
-
-def extract_pem_certs(b_data):
- for match in b_PEM_CERT_RE.finditer(b_data):
+ This method isn't of much use any longer, but is kept
+ in a minimal state for backwards compat.
+ '''
+ result = ParseResultDottedDict(parts._asdict())
+ result.update({
+ 'username': parts.username,
+ 'password': parts.password,
+ 'hostname': parts.hostname,
+ 'port': parts.port,
+ })
+ return result
+
+
+def extract_pem_certs(data):
+ for match in PEM_CERT_RE.finditer(data):
yield match.group(0)
-def _py2_get_param(headers, param, header='content-type'):
- m = httplib.HTTPMessage(io.StringIO())
- cd = headers.getheader(header) or ''
- try:
- m.plisttext = cd[cd.index(';'):]
- m.parseplist()
- except ValueError:
- return None
-
- return m.getparam(param)
-
-
def get_response_filename(response):
url = response.geturl()
path = urlparse(url)[2]
@@ -790,22 +346,12 @@ def get_response_filename(response):
if filename:
filename = unquote(filename)
- if PY2:
- get_param = functools.partial(_py2_get_param, response.headers)
- else:
- get_param = response.headers.get_param
-
- return get_param('filename', header='content-disposition') or filename
+ return response.headers.get_param('filename', header='content-disposition') or filename
def parse_content_type(response):
- if PY2:
- get_type = response.headers.gettype
- get_param = response.headers.getparam
- else:
- get_type = response.headers.get_content_type
- get_param = response.headers.get_param
-
+ get_type = response.headers.get_content_type
+ get_param = response.headers.get_param
content_type = (get_type() or 'application/octet-stream').split(',')[0]
main_type, sub_type = content_type.split('/')
charset = (get_param('charset') or 'utf-8').split(',')[0]
@@ -822,17 +368,8 @@ class GzipDecodedReader(GzipFile):
if not HAS_GZIP:
raise MissingModuleError(self.missing_gzip_error(), import_traceback=GZIP_IMP_ERR)
- if PY3:
- self._io = fp
- else:
- # Py2 ``HTTPResponse``/``addinfourl`` doesn't support all of the file object
- # functionality GzipFile requires
- self._io = io.BytesIO()
- for block in iter(functools.partial(fp.read, 65536), b''):
- self._io.write(block)
- self._io.seek(0)
- fp.close()
- gzip.GzipFile.__init__(self, mode='rb', fileobj=self._io) # pylint: disable=non-parent-init-called
+ self._io = fp
+ super().__init__(mode='rb', fileobj=self._io)
def close(self):
try:
@@ -849,432 +386,206 @@ class GzipDecodedReader(GzipFile):
)
-class RequestWithMethod(urllib_request.Request):
- '''
- Workaround for using DELETE/PUT/etc with urllib2
- Originally contained in library/net_infrastructure/dnsmadeeasy
- '''
-
- def __init__(self, url, method, data=None, headers=None, origin_req_host=None, unverifiable=True):
- if headers is None:
- headers = {}
- self._method = method.upper()
- urllib_request.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
-
- def get_method(self):
- if self._method:
- return self._method
- else:
- return urllib_request.Request.get_method(self)
-
-
-def RedirectHandlerFactory(follow_redirects=None, validate_certs=True, ca_path=None, ciphers=None):
- """This is a class factory that closes over the value of
- ``follow_redirects`` so that the RedirectHandler class has access to
- that value without having to use globals, and potentially cause problems
- where ``open_url`` or ``fetch_url`` are used multiple times in a module.
+class HTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
+ """This is an implementation of a RedirectHandler to match the
+ functionality provided by httplib2. It will utilize the value of
+ ``follow_redirects`` to determine how redirects should be handled in
+ urllib.
"""
- class RedirectHandler(urllib_request.HTTPRedirectHandler):
- """This is an implementation of a RedirectHandler to match the
- functionality provided by httplib2. It will utilize the value of
- ``follow_redirects`` that is passed into ``RedirectHandlerFactory``
- to determine how redirects should be handled in urllib2.
- """
-
- def redirect_request(self, req, fp, code, msg, headers, newurl):
- if not any((HAS_SSLCONTEXT, HAS_URLLIB3_PYOPENSSLCONTEXT)):
- handler = maybe_add_ssl_handler(newurl, validate_certs, ca_path=ca_path, ciphers=ciphers)
- if handler:
- urllib_request._opener.add_handler(handler)
-
- # Preserve urllib2 compatibility
- if follow_redirects == 'urllib2':
- return urllib_request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
-
- # Handle disabled redirects
- elif follow_redirects in ['no', 'none', False]:
- raise urllib_error.HTTPError(newurl, code, msg, headers, fp)
-
- method = req.get_method()
-
- # Handle non-redirect HTTP status or invalid follow_redirects
- if follow_redirects in ['all', 'yes', True]:
- if code < 300 or code >= 400:
- raise urllib_error.HTTPError(req.get_full_url(), code, msg, headers, fp)
- elif follow_redirects == 'safe':
- if code < 300 or code >= 400 or method not in ('GET', 'HEAD'):
- raise urllib_error.HTTPError(req.get_full_url(), code, msg, headers, fp)
- else:
- raise urllib_error.HTTPError(req.get_full_url(), code, msg, headers, fp)
+ def __init__(self, follow_redirects=None):
+ self.follow_redirects = follow_redirects
- try:
- # Python 2-3.3
- data = req.get_data()
- origin_req_host = req.get_origin_req_host()
- except AttributeError:
- # Python 3.4+
- data = req.data
- origin_req_host = req.origin_req_host
+ def __call__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ return self
- # Be conciliant with URIs containing a space
- newurl = newurl.replace(' ', '%20')
+ try:
+ urllib.request.HTTPRedirectHandler.http_error_308 # type: ignore[attr-defined]
+ except AttributeError:
+ # deprecated: description='urllib http 308 support' python_version='3.11'
+ http_error_308 = urllib.request.HTTPRedirectHandler.http_error_302
- # Support redirect with payload and original headers
- if code in (307, 308):
- # Preserve payload and headers
- req_headers = req.headers
- else:
- # Do not preserve payload and filter headers
- data = None
- req_headers = dict((k, v) for k, v in req.headers.items()
- if k.lower() not in ("content-length", "content-type", "transfer-encoding"))
-
- # http://tools.ietf.org/html/rfc7231#section-6.4.4
- if code == 303 and method != 'HEAD':
- method = 'GET'
-
- # Do what the browsers do, despite standards...
- # First, turn 302s into GETs.
- if code == 302 and method != 'HEAD':
- method = 'GET'
-
- # Second, if a POST is responded to with a 301, turn it into a GET.
- if code == 301 and method == 'POST':
- method = 'GET'
-
- return RequestWithMethod(newurl,
- method=method,
- headers=req_headers,
- data=data,
- origin_req_host=origin_req_host,
- unverifiable=True,
- )
-
- return RedirectHandler
-
-
-def build_ssl_validation_error(hostname, port, paths, exc=None):
- '''Inteligently build out the SSLValidationError based on what support
- you have installed
- '''
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
+ follow_redirects = self.follow_redirects
- msg = [
- ('Failed to validate the SSL certificate for %s:%s.'
- ' Make sure your managed systems have a valid CA'
- ' certificate installed.')
- ]
- if not HAS_SSLCONTEXT:
- msg.append('If the website serving the url uses SNI you need'
- ' python >= 2.7.9 on your managed machine')
- msg.append(' (the python executable used (%s) is version: %s)' %
- (sys.executable, ''.join(sys.version.splitlines())))
- if not HAS_URLLIB3_PYOPENSSLCONTEXT and not HAS_URLLIB3_SSL_WRAP_SOCKET:
- msg.append('or you can install the `urllib3`, `pyOpenSSL`,'
- ' `ndg-httpsclient`, and `pyasn1` python modules')
+ # Preserve urllib2 compatibility
+ if follow_redirects in ('urllib2', 'urllib'):
+ return urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
- msg.append('to perform SNI verification in python >= 2.6.')
+ # Handle disabled redirects
+ elif follow_redirects in ('no', 'none', False):
+ raise urllib.error.HTTPError(newurl, code, msg, headers, fp)
- msg.append('You can use validate_certs=False if you do'
- ' not need to confirm the servers identity but this is'
- ' unsafe and not recommended.'
- ' Paths checked for this platform: %s.')
+ method = req.get_method()
- if exc:
- msg.append('The exception msg was: %s.' % to_native(exc))
+ # Handle non-redirect HTTP status or invalid follow_redirects
+ if follow_redirects in ('all', 'yes', True):
+ if code < 300 or code >= 400:
+ raise urllib.error.HTTPError(req.get_full_url(), code, msg, headers, fp)
+ elif follow_redirects == 'safe':
+ if code < 300 or code >= 400 or method not in ('GET', 'HEAD'):
+ raise urllib.error.HTTPError(req.get_full_url(), code, msg, headers, fp)
+ else:
+ raise urllib.error.HTTPError(req.get_full_url(), code, msg, headers, fp)
- raise SSLValidationError(' '.join(msg) % (hostname, port, ", ".join(paths)))
+ data = req.data
+ origin_req_host = req.origin_req_host
+ # Be conciliant with URIs containing a space
+ newurl = newurl.replace(' ', '%20')
-def atexit_remove_file(filename):
- if os.path.exists(filename):
- try:
- os.unlink(filename)
- except Exception:
- # just ignore if we cannot delete, things should be ok
- pass
+ # Support redirect with payload and original headers
+ if code in (307, 308):
+ # Preserve payload and headers
+ req_headers = req.headers
+ else:
+ # Do not preserve payload and filter headers
+ data = None
+ req_headers = {k: v for k, v in req.headers.items()
+ if k.lower() not in ("content-length", "content-type", "transfer-encoding")}
+
+ # http://tools.ietf.org/html/rfc7231#section-6.4.4
+ if code == 303 and method != 'HEAD':
+ method = 'GET'
+
+ # Do what the browsers do, despite standards...
+ # First, turn 302s into GETs.
+ if code == 302 and method != 'HEAD':
+ method = 'GET'
+
+ # Second, if a POST is responded to with a 301, turn it into a GET.
+ if code == 301 and method == 'POST':
+ method = 'GET'
+
+ return urllib.request.Request(
+ newurl,
+ data=data,
+ headers=req_headers,
+ origin_req_host=origin_req_host,
+ unverifiable=True,
+ method=method.upper(),
+ )
-def make_context(cafile=None, cadata=None, ciphers=None, validate_certs=True, client_cert=None, client_key=None):
+def make_context(cafile=None, cadata=None, capath=None, ciphers=None, validate_certs=True, client_cert=None,
+ client_key=None):
if ciphers is None:
ciphers = []
if not is_sequence(ciphers):
raise TypeError('Ciphers must be a list. Got %s.' % ciphers.__class__.__name__)
- if HAS_SSLCONTEXT:
- context = create_default_context(cafile=cafile)
- elif HAS_URLLIB3_PYOPENSSLCONTEXT:
- context = PyOpenSSLContext(PROTOCOL)
- else:
- raise NotImplementedError('Host libraries are too old to support creating an sslcontext')
+ context = ssl.create_default_context(cafile=cafile)
if not validate_certs:
- if ssl.OP_NO_SSLv2:
- context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
- if validate_certs and any((cafile, cadata)):
- context.load_verify_locations(cafile=cafile, cadata=cadata)
+ # If cafile is passed, we are only using that for verification,
+ # don't add additional ca certs
+ if validate_certs and not cafile:
+ if not cadata:
+ cadata = bytearray()
+ cadata.extend(get_ca_certs(capath=capath)[0])
+ if cadata:
+ context.load_verify_locations(cadata=cadata)
if ciphers:
context.set_ciphers(':'.join(map(to_native, ciphers)))
if client_cert:
+ # TLS 1.3 needs this to be set to True to allow post handshake cert
+ # authentication. This functionality was added in Python 3.8 and was
+ # backported to 3.6.7, and 3.7.1 so needs a check for now.
+ if hasattr(context, "post_handshake_auth"):
+ context.post_handshake_auth = True
+
context.load_cert_chain(client_cert, keyfile=client_key)
return context
-def get_ca_certs(cafile=None):
+def get_ca_certs(cafile=None, capath=None):
# tries to find a valid CA cert in one of the
# standard locations for the current distribution
- cadata = bytearray()
- paths_checked = []
+ # Using a dict, instead of a set for order, the value is meaningless and will be None
+ # Not directly using a bytearray to avoid duplicates with fast lookup
+ cadata = {}
+ # If cafile is passed, we are only using that for verification,
+ # don't add additional ca certs
if cafile:
paths_checked = [cafile]
- with open(to_bytes(cafile, errors='surrogate_or_strict'), 'rb') as f:
- if HAS_SSLCONTEXT:
- for b_pem in extract_pem_certs(f.read()):
- cadata.extend(
- ssl.PEM_cert_to_DER_cert(
- to_native(b_pem, errors='surrogate_or_strict')
- )
- )
- return cafile, cadata, paths_checked
-
- if not HAS_SSLCONTEXT:
- paths_checked.append('/etc/ssl/certs')
+ with open(to_bytes(cafile, errors='surrogate_or_strict'), 'r', errors='surrogateescape') as f:
+ for pem in extract_pem_certs(f.read()):
+ b_der = ssl.PEM_cert_to_DER_cert(pem)
+ cadata[b_der] = None
+ return bytearray().join(cadata), paths_checked
+
+ default_verify_paths = ssl.get_default_verify_paths()
+ default_capath = default_verify_paths.capath
+ paths_checked = {default_capath or default_verify_paths.cafile}
+
+ if capath:
+ paths_checked.add(capath)
system = to_text(platform.system(), errors='surrogate_or_strict')
# build a list of paths to check for .crt/.pem files
# based on the platform type
if system == u'Linux':
- paths_checked.append('/etc/pki/ca-trust/extracted/pem')
- paths_checked.append('/etc/pki/tls/certs')
- paths_checked.append('/usr/share/ca-certificates/cacert.org')
+ paths_checked.add('/etc/pki/ca-trust/extracted/pem')
+ paths_checked.add('/etc/pki/tls/certs')
+ paths_checked.add('/usr/share/ca-certificates/cacert.org')
elif system == u'FreeBSD':
- paths_checked.append('/usr/local/share/certs')
+ paths_checked.add('/usr/local/share/certs')
elif system == u'OpenBSD':
- paths_checked.append('/etc/ssl')
+ paths_checked.add('/etc/ssl')
elif system == u'NetBSD':
- paths_checked.append('/etc/openssl/certs')
+ paths_checked.add('/etc/openssl/certs')
elif system == u'SunOS':
- paths_checked.append('/opt/local/etc/openssl/certs')
+ paths_checked.add('/opt/local/etc/openssl/certs')
elif system == u'AIX':
- paths_checked.append('/var/ssl/certs')
- paths_checked.append('/opt/freeware/etc/ssl/certs')
+ paths_checked.add('/var/ssl/certs')
+ paths_checked.add('/opt/freeware/etc/ssl/certs')
+ elif system == u'Darwin':
+ paths_checked.add('/usr/local/etc/openssl')
# fall back to a user-deployed cert in a standard
# location if the OS platform one is not available
- paths_checked.append('/etc/ansible')
-
- tmp_path = None
- if not HAS_SSLCONTEXT:
- tmp_fd, tmp_path = tempfile.mkstemp()
- atexit.register(atexit_remove_file, tmp_path)
-
- # Write the dummy ca cert if we are running on macOS
- if system == u'Darwin':
- if HAS_SSLCONTEXT:
- cadata.extend(
- ssl.PEM_cert_to_DER_cert(
- to_native(b_DUMMY_CA_CERT, errors='surrogate_or_strict')
- )
- )
- else:
- os.write(tmp_fd, b_DUMMY_CA_CERT)
- # Default Homebrew path for OpenSSL certs
- paths_checked.append('/usr/local/etc/openssl')
+ paths_checked.add('/etc/ansible')
# for all of the paths, find any .crt or .pem files
# and compile them into single temp file for use
# in the ssl check to speed up the test
for path in paths_checked:
- if not os.path.isdir(path):
+ if not path or path == default_capath or not os.path.isdir(path):
continue
- dir_contents = os.listdir(path)
- for f in dir_contents:
+ for f in os.listdir(path):
full_path = os.path.join(path, f)
- if os.path.isfile(full_path) and os.path.splitext(f)[1] in ('.crt', '.pem'):
+ if os.path.isfile(full_path) and os.path.splitext(f)[1] in {'.pem', '.cer', '.crt'}:
try:
- if full_path not in LOADED_VERIFY_LOCATIONS:
- with open(full_path, 'rb') as cert_file:
- b_cert = cert_file.read()
- if HAS_SSLCONTEXT:
- try:
- for b_pem in extract_pem_certs(b_cert):
- cadata.extend(
- ssl.PEM_cert_to_DER_cert(
- to_native(b_pem, errors='surrogate_or_strict')
- )
- )
- except Exception:
- continue
- else:
- os.write(tmp_fd, b_cert)
- os.write(tmp_fd, b'\n')
+ with open(full_path, 'r', errors='surrogateescape') as cert_file:
+ cert = cert_file.read()
+ try:
+ for pem in extract_pem_certs(cert):
+ b_der = ssl.PEM_cert_to_DER_cert(pem)
+ cadata[b_der] = None
+ except Exception:
+ continue
except (OSError, IOError):
pass
- if HAS_SSLCONTEXT:
- default_verify_paths = ssl.get_default_verify_paths()
- paths_checked[:0] = [default_verify_paths.capath]
- else:
- os.close(tmp_fd)
-
- return (tmp_path, cadata, paths_checked)
-
-
-class SSLValidationHandler(urllib_request.BaseHandler):
- '''
- A custom handler class for SSL validation.
-
- Based on:
- http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-python
- http://techknack.net/python-urllib2-handlers/
- '''
- CONNECT_COMMAND = "CONNECT %s:%s HTTP/1.0\r\n"
-
- def __init__(self, hostname, port, ca_path=None, ciphers=None, validate_certs=True):
- self.hostname = hostname
- self.port = port
- self.ca_path = ca_path
- self.ciphers = ciphers
- self.validate_certs = validate_certs
-
- def get_ca_certs(self):
- return get_ca_certs(self.ca_path)
-
- def validate_proxy_response(self, response, valid_codes=None):
- '''
- make sure we get back a valid code from the proxy
- '''
- valid_codes = [200] if valid_codes is None else valid_codes
-
- try:
- (http_version, resp_code, msg) = re.match(br'(HTTP/\d\.\d) (\d\d\d) (.*)', response).groups()
- if int(resp_code) not in valid_codes:
- raise Exception
- except Exception:
- raise ProxyError('Connection to proxy failed')
-
- def detect_no_proxy(self, url):
- '''
- Detect if the 'no_proxy' environment variable is set and honor those locations.
- '''
- env_no_proxy = os.environ.get('no_proxy')
- if env_no_proxy:
- env_no_proxy = env_no_proxy.split(',')
- netloc = urlparse(url).netloc
-
- for host in env_no_proxy:
- if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
- # Our requested URL matches something in no_proxy, so don't
- # use the proxy for this
- return False
- return True
-
- def make_context(self, cafile, cadata, ciphers=None, validate_certs=True):
- cafile = self.ca_path or cafile
- if self.ca_path:
- cadata = None
- else:
- cadata = cadata or None
-
- return make_context(cafile=cafile, cadata=cadata, ciphers=ciphers, validate_certs=validate_certs)
-
- def http_request(self, req):
- tmp_ca_cert_path, cadata, paths_checked = self.get_ca_certs()
-
- # Detect if 'no_proxy' environment variable is set and if our URL is included
- use_proxy = self.detect_no_proxy(req.get_full_url())
- https_proxy = os.environ.get('https_proxy')
-
- context = None
- try:
- context = self.make_context(tmp_ca_cert_path, cadata, ciphers=self.ciphers, validate_certs=self.validate_certs)
- except NotImplementedError:
- # We'll make do with no context below
- pass
-
- try:
- if use_proxy and https_proxy:
- proxy_parts = generic_urlparse(urlparse(https_proxy))
- port = proxy_parts.get('port') or 443
- proxy_hostname = proxy_parts.get('hostname', None)
- if proxy_hostname is None or proxy_parts.get('scheme') == '':
- raise ProxyError("Failed to parse https_proxy environment variable."
- " Please make sure you export https proxy as 'https_proxy=<SCHEME>://<IP_ADDRESS>:<PORT>'")
-
- s = socket.create_connection((proxy_hostname, port))
- if proxy_parts.get('scheme') == 'http':
- s.sendall(to_bytes(self.CONNECT_COMMAND % (self.hostname, self.port), errors='surrogate_or_strict'))
- if proxy_parts.get('username'):
- credentials = "%s:%s" % (proxy_parts.get('username', ''), proxy_parts.get('password', ''))
- s.sendall(b'Proxy-Authorization: Basic %s\r\n' % base64.b64encode(to_bytes(credentials, errors='surrogate_or_strict')).strip())
- s.sendall(b'\r\n')
- connect_result = b""
- while connect_result.find(b"\r\n\r\n") <= 0:
- connect_result += s.recv(4096)
- # 128 kilobytes of headers should be enough for everyone.
- if len(connect_result) > 131072:
- raise ProxyError('Proxy sent too verbose headers. Only 128KiB allowed.')
- self.validate_proxy_response(connect_result)
- if context:
- ssl_s = context.wrap_socket(s, server_hostname=self.hostname)
- elif HAS_URLLIB3_SSL_WRAP_SOCKET:
- ssl_s = ssl_wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL, server_hostname=self.hostname)
- else:
- ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL)
- match_hostname(ssl_s.getpeercert(), self.hostname)
- else:
- raise ProxyError('Unsupported proxy scheme: %s. Currently ansible only supports HTTP proxies.' % proxy_parts.get('scheme'))
- else:
- s = socket.create_connection((self.hostname, self.port))
- if context:
- ssl_s = context.wrap_socket(s, server_hostname=self.hostname)
- elif HAS_URLLIB3_SSL_WRAP_SOCKET:
- ssl_s = ssl_wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL, server_hostname=self.hostname)
- else:
- ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL)
- match_hostname(ssl_s.getpeercert(), self.hostname)
- # close the ssl connection
- # ssl_s.unwrap()
- s.close()
- except (ssl.SSLError, CertificateError) as e:
- build_ssl_validation_error(self.hostname, self.port, paths_checked, e)
- except socket.error as e:
- raise ConnectionError('Failed to connect to %s at port %s: %s' % (self.hostname, self.port, to_native(e)))
-
- return req
-
- https_request = http_request
-
-
-def maybe_add_ssl_handler(url, validate_certs, ca_path=None, ciphers=None):
- parsed = generic_urlparse(urlparse(url))
- if parsed.scheme == 'https' and validate_certs:
- if not HAS_SSL:
- raise NoSSLError('SSL validation is not available in your version of python. You can use validate_certs=False,'
- ' however this is unsafe and not recommended')
-
- # create the SSL validation handler
- return SSLValidationHandler(parsed.hostname, parsed.port or 443, ca_path=ca_path, ciphers=ciphers, validate_certs=validate_certs)
+ # paths_checked isn't used any more, but is kept just for ease of debugging
+ return bytearray().join(cadata), list(paths_checked)
def getpeercert(response, binary_form=False):
""" Attempt to get the peer certificate of the response from urlopen. """
- # The response from urllib2.open() is different across Python 2 and 3
- if PY3:
- socket = response.fp.raw._sock
- else:
- socket = response.fp._sock.fp._sock
+ socket = response.fp.raw._sock
try:
return socket.getpeercert(binary_form)
@@ -1297,7 +608,7 @@ def get_channel_binding_cert_hash(certificate_der):
pass
# If the signature hash algorithm is unknown/unsupported or md5/sha1 we must use SHA256.
- if not hash_algorithm or hash_algorithm.name in ['md5', 'sha1']:
+ if not hash_algorithm or hash_algorithm.name in ('md5', 'sha1'):
hash_algorithm = hashes.SHA256()
digest = hashes.Hash(hash_algorithm, default_backend())
@@ -1322,11 +633,80 @@ def rfc2822_date_string(timetuple, zone='-0000'):
zone)
+def _configure_auth(url, url_username, url_password, use_gssapi, force_basic_auth, use_netrc):
+ headers = {}
+ handlers = []
+
+ parsed = urlparse(url)
+ if parsed.scheme == 'ftp':
+ return url, headers, handlers
+
+ username = url_username
+ password = url_password
+
+ if username:
+ netloc = parsed.netloc
+ elif '@' in parsed.netloc:
+ credentials, netloc = parsed.netloc.split('@', 1)
+ if ':' in credentials:
+ username, password = credentials.split(':', 1)
+ else:
+ username = credentials
+ password = ''
+ username = unquote(username)
+ password = unquote(password)
+
+ # reconstruct url without credentials
+ url = urlunparse(parsed._replace(netloc=netloc))
+
+ if use_gssapi:
+ if HTTPGSSAPIAuthHandler: # type: ignore[truthy-function]
+ handlers.append(HTTPGSSAPIAuthHandler(username, password))
+ else:
+ imp_err_msg = missing_required_lib('gssapi', reason='for use_gssapi=True',
+ url='https://pypi.org/project/gssapi/')
+ raise MissingModuleError(imp_err_msg, import_traceback=GSSAPI_IMP_ERR)
+
+ elif username and not force_basic_auth:
+ passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
+
+ # this creates a password manager
+ passman.add_password(None, netloc, username, password)
+
+ # because we have put None at the start it will always
+ # use this username/password combination for urls
+ # for which `theurl` is a super-url
+ authhandler = urllib.request.HTTPBasicAuthHandler(passman)
+ digest_authhandler = urllib.request.HTTPDigestAuthHandler(passman)
+
+ # create the AuthHandler
+ handlers.append(authhandler)
+ handlers.append(digest_authhandler)
+
+ elif username and force_basic_auth:
+ headers["Authorization"] = basic_auth_header(username, password)
+
+ elif use_netrc:
+ try:
+ rc = netrc.netrc(os.environ.get('NETRC'))
+ login = rc.authenticators(parsed.hostname)
+ except IOError:
+ login = None
+
+ if login:
+ username, dummy, password = login
+ if username and password:
+ headers["Authorization"] = basic_auth_header(username, password)
+
+ return url, headers, handlers
+
+
class Request:
def __init__(self, headers=None, use_proxy=True, force=False, timeout=10, validate_certs=True,
url_username=None, url_password=None, http_agent=None, force_basic_auth=False,
follow_redirects='urllib2', client_cert=None, client_key=None, cookies=None, unix_socket=None,
- ca_path=None, unredirected_headers=None, decompress=True, ciphers=None, use_netrc=True):
+ ca_path=None, unredirected_headers=None, decompress=True, ciphers=None, use_netrc=True,
+ context=None):
"""This class works somewhat similarly to the ``Session`` class of from requests
by defining a cookiejar that can be used across requests as well as cascaded defaults that
can apply to repeated requests
@@ -1365,6 +745,7 @@ class Request:
self.decompress = decompress
self.ciphers = ciphers
self.use_netrc = use_netrc
+ self.context = context
if isinstance(cookies, cookiejar.CookieJar):
self.cookies = cookies
else:
@@ -1381,9 +762,9 @@ class Request:
force_basic_auth=None, follow_redirects=None,
client_cert=None, client_key=None, cookies=None, use_gssapi=False,
unix_socket=None, ca_path=None, unredirected_headers=None, decompress=None,
- ciphers=None, use_netrc=None):
+ ciphers=None, use_netrc=None, context=None):
"""
- Sends a request via HTTP(S) or FTP using urllib2 (Python2) or urllib (Python3)
+ Sends a request via HTTP(S) or FTP using urllib (Python3)
Does not require the module environment
@@ -1408,7 +789,7 @@ class Request:
:kwarg http_agent: (optional) String of the User-Agent to use in the request
:kwarg force_basic_auth: (optional) Boolean determining if auth header should be sent in the initial request
:kwarg follow_redirects: (optional) String of urllib2, all/yes, safe, none to determine how redirects are
- followed, see RedirectHandlerFactory for more information
+ followed, see HTTPRedirectHandler for more information
:kwarg client_cert: (optional) PEM formatted certificate chain file to be used for SSL client authentication.
This file can also include the key as well, and if the key is included, client_key is not required
:kwarg client_key: (optional) PEM formatted file that contains your private key to be used for SSL client
@@ -1423,11 +804,11 @@ class Request:
:kwarg decompress: (optional) Whether to attempt to decompress gzip content-encoded responses
:kwarg ciphers: (optional) List of ciphers to use
:kwarg use_netrc: (optional) Boolean determining whether to use credentials from ~/.netrc file
+ :kwarg context: (optional) ssl.Context object for SSL validation. When provided, all other SSL related
+ arguments are ignored. See make_context.
:returns: HTTPResponse. Added in Ansible 2.9
"""
- method = method.upper()
-
if headers is None:
headers = {}
elif not isinstance(headers, dict):
@@ -1452,106 +833,46 @@ class Request:
decompress = self._fallback(decompress, self.decompress)
ciphers = self._fallback(ciphers, self.ciphers)
use_netrc = self._fallback(use_netrc, self.use_netrc)
+ context = self._fallback(context, self.context)
handlers = []
if unix_socket:
handlers.append(UnixHTTPHandler(unix_socket))
- parsed = generic_urlparse(urlparse(url))
- if parsed.scheme != 'ftp':
- username = url_username
- password = url_password
-
- if username:
- netloc = parsed.netloc
- elif '@' in parsed.netloc:
- credentials, netloc = parsed.netloc.split('@', 1)
- if ':' in credentials:
- username, password = credentials.split(':', 1)
- else:
- username = credentials
- password = ''
-
- parsed_list = parsed.as_list()
- parsed_list[1] = netloc
-
- # reconstruct url without credentials
- url = urlunparse(parsed_list)
-
- if use_gssapi:
- if HTTPGSSAPIAuthHandler: # type: ignore[truthy-function]
- handlers.append(HTTPGSSAPIAuthHandler(username, password))
- else:
- imp_err_msg = missing_required_lib('gssapi', reason='for use_gssapi=True',
- url='https://pypi.org/project/gssapi/')
- raise MissingModuleError(imp_err_msg, import_traceback=GSSAPI_IMP_ERR)
-
- elif username and not force_basic_auth:
- passman = urllib_request.HTTPPasswordMgrWithDefaultRealm()
-
- # this creates a password manager
- passman.add_password(None, netloc, username, password)
-
- # because we have put None at the start it will always
- # use this username/password combination for urls
- # for which `theurl` is a super-url
- authhandler = urllib_request.HTTPBasicAuthHandler(passman)
- digest_authhandler = urllib_request.HTTPDigestAuthHandler(passman)
-
- # create the AuthHandler
- handlers.append(authhandler)
- handlers.append(digest_authhandler)
-
- elif username and force_basic_auth:
- headers["Authorization"] = basic_auth_header(username, password)
-
- elif use_netrc:
- try:
- rc = netrc.netrc(os.environ.get('NETRC'))
- login = rc.authenticators(parsed.hostname)
- except IOError:
- login = None
-
- if login:
- username, dummy, password = login
- if username and password:
- headers["Authorization"] = basic_auth_header(username, password)
+ url, auth_headers, auth_handlers = _configure_auth(url, url_username, url_password, use_gssapi, force_basic_auth, use_netrc)
+ headers.update(auth_headers)
+ handlers.extend(auth_handlers)
if not use_proxy:
- proxyhandler = urllib_request.ProxyHandler({})
+ proxyhandler = urllib.request.ProxyHandler({})
handlers.append(proxyhandler)
- if not any((HAS_SSLCONTEXT, HAS_URLLIB3_PYOPENSSLCONTEXT)):
- ssl_handler = maybe_add_ssl_handler(url, validate_certs, ca_path=ca_path, ciphers=ciphers)
- if ssl_handler:
- handlers.append(ssl_handler)
- else:
- tmp_ca_path, cadata, paths_checked = get_ca_certs(ca_path)
+ if not context:
context = make_context(
- cafile=tmp_ca_path,
- cadata=cadata,
+ cafile=ca_path,
ciphers=ciphers,
validate_certs=validate_certs,
client_cert=client_cert,
client_key=client_key,
)
- handlers.append(HTTPSClientAuthHandler(client_cert=client_cert,
- client_key=client_key,
- unix_socket=unix_socket,
- context=context))
+ if unix_socket:
+ ssl_handler = UnixHTTPSHandler(unix_socket=unix_socket, context=context)
+ else:
+ ssl_handler = urllib.request.HTTPSHandler(context=context)
+ handlers.append(ssl_handler)
- handlers.append(RedirectHandlerFactory(follow_redirects, validate_certs, ca_path=ca_path, ciphers=ciphers))
+ handlers.append(HTTPRedirectHandler(follow_redirects))
# add some nicer cookie handling
if cookies is not None:
- handlers.append(urllib_request.HTTPCookieProcessor(cookies))
+ handlers.append(urllib.request.HTTPCookieProcessor(cookies))
- opener = urllib_request.build_opener(*handlers)
- urllib_request.install_opener(opener)
+ opener = urllib.request.build_opener(*handlers)
+ urllib.request.install_opener(opener)
data = to_bytes(data, nonstring='passthru')
- request = RequestWithMethod(url, method, data)
+ request = urllib.request.Request(url, data=data, method=method.upper())
# add the custom agent header, to help prevent issues
# with sites that block the default urllib agent string
@@ -1575,25 +896,13 @@ class Request:
else:
request.add_header(header, headers[header])
- r = urllib_request.urlopen(request, None, timeout)
+ r = urllib.request.urlopen(request, None, timeout)
if decompress and r.headers.get('content-encoding', '').lower() == 'gzip':
fp = GzipDecodedReader(r.fp)
- if PY3:
- r.fp = fp
- # Content-Length does not match gzip decoded length
- # Prevent ``r.read`` from stopping at Content-Length
- r.length = None
- else:
- # Py2 maps ``r.read`` to ``fp.read``, create new ``addinfourl``
- # object to compensate
- msg = r.msg
- r = urllib_request.addinfourl(
- fp,
- r.info(),
- r.geturl(),
- r.getcode()
- )
- r.msg = msg
+ r.fp = fp
+ # Content-Length does not match gzip decoded length
+ # Prevent ``r.read`` from stopping at Content-Length
+ r.length = None
return r
def get(self, url, **kwargs):
@@ -1678,7 +987,7 @@ def open_url(url, data=None, headers=None, method=None, use_proxy=True,
use_gssapi=False, unix_socket=None, ca_path=None,
unredirected_headers=None, decompress=True, ciphers=None, use_netrc=True):
'''
- Sends a request via HTTP(S) or FTP using urllib2 (Python2) or urllib (Python3)
+ Sends a request via HTTP(S) or FTP using urllib (Python3)
Does not require the module environment
'''
@@ -1726,7 +1035,7 @@ def prepare_multipart(fields):
m = email.mime.multipart.MIMEMultipart('form-data')
for field, value in sorted(fields.items()):
- if isinstance(value, string_types):
+ if isinstance(value, str):
main_type = 'text'
sub_type = 'plain'
content = value
@@ -1774,30 +1083,15 @@ def prepare_multipart(fields):
m.attach(part)
- if PY3:
- # Ensure headers are not split over multiple lines
- # The HTTP policy also uses CRLF by default
- b_data = m.as_bytes(policy=email.policy.HTTP)
- else:
- # Py2
- # We cannot just call ``as_string`` since it provides no way
- # to specify ``maxheaderlen``
- fp = cStringIO() # cStringIO seems to be required here
- # Ensure headers are not split over multiple lines
- g = email.generator.Generator(fp, maxheaderlen=0)
- g.flatten(m)
- # ``fix_eols`` switches from ``\n`` to ``\r\n``
- b_data = email.utils.fix_eols(fp.getvalue())
+ # Ensure headers are not split over multiple lines
+ # The HTTP policy also uses CRLF by default
+ b_data = m.as_bytes(policy=email.policy.HTTP)
del m
headers, sep, b_content = b_data.partition(b'\r\n\r\n')
del b_data
- if PY3:
- parser = email.parser.BytesHeaderParser().parsebytes
- else:
- # Py2
- parser = email.parser.HeaderParser().parsestr
+ parser = email.parser.BytesHeaderParser().parsebytes
return (
parser(headers)['content-type'], # Message converts to native strings
@@ -1883,9 +1177,6 @@ def fetch_url(module, url, data=None, headers=None, method=None,
body = info['body']
"""
- if not HAS_URLPARSE:
- module.fail_json(msg='urlparse is not installed')
-
if not HAS_GZIP:
module.fail_json(msg=GzipDecodedReader.missing_gzip_error())
@@ -1911,7 +1202,7 @@ def fetch_url(module, url, data=None, headers=None, method=None,
use_gssapi = module.params.get('use_gssapi', use_gssapi)
if not isinstance(cookies, cookiejar.CookieJar):
- cookies = cookiejar.LWPCookieJar()
+ cookies = cookiejar.CookieJar()
r = None
info = dict(url=url, status=-1)
@@ -1924,25 +1215,23 @@ def fetch_url(module, url, data=None, headers=None, method=None,
client_key=client_key, cookies=cookies, use_gssapi=use_gssapi,
unix_socket=unix_socket, ca_path=ca_path, unredirected_headers=unredirected_headers,
decompress=decompress, ciphers=ciphers, use_netrc=use_netrc)
- # Lowercase keys, to conform to py2 behavior, so that py3 and py2 are predictable
- info.update(dict((k.lower(), v) for k, v in r.info().items()))
+ # Lowercase keys, to conform to py2 behavior
+ info.update({k.lower(): v for k, v in r.info().items()})
# Don't be lossy, append header values for duplicate headers
- # In Py2 there is nothing that needs done, py2 does this for us
- if PY3:
- temp_headers = {}
- for name, value in r.headers.items():
- # The same as above, lower case keys to match py2 behavior, and create more consistent results
- name = name.lower()
- if name in temp_headers:
- temp_headers[name] = ', '.join((temp_headers[name], value))
- else:
- temp_headers[name] = value
- info.update(temp_headers)
+ temp_headers = {}
+ for name, value in r.headers.items():
+ # The same as above, lower case keys to match py2 behavior, and create more consistent results
+ name = name.lower()
+ if name in temp_headers:
+ temp_headers[name] = ', '.join((temp_headers[name], value))
+ else:
+ temp_headers[name] = value
+ info.update(temp_headers)
# parse the cookies into a nice dictionary
cookie_list = []
- cookie_dict = dict()
+ cookie_dict = {}
# Python sorts cookies in order of most specific (ie. longest) path first. See ``CookieJar._cookie_attrs``
# Cookies with the same path are reversed from response order.
# This code makes no assumptions about that, and accepts the order given by python
@@ -1954,17 +1243,11 @@ def fetch_url(module, url, data=None, headers=None, method=None,
info['cookies'] = cookie_dict
# finally update the result with a message about the fetch
info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), url=r.geturl(), status=r.code))
- except NoSSLError as e:
- distribution = get_distribution()
- if distribution is not None and distribution.lower() == 'redhat':
- module.fail_json(msg='%s. You can also install python-ssl from EPEL' % to_native(e), **info)
- else:
- module.fail_json(msg='%s' % to_native(e), **info)
except (ConnectionError, ValueError) as e:
module.fail_json(msg=to_native(e), **info)
except MissingModuleError as e:
module.fail_json(msg=to_text(e), exception=e.import_traceback)
- except urllib_error.HTTPError as e:
+ except urllib.error.HTTPError as e:
r = e
try:
if e.fp is None:
@@ -1981,18 +1264,18 @@ def fetch_url(module, url, data=None, headers=None, method=None,
# Try to add exception info to the output but don't fail if we can't
try:
# Lowercase keys, to conform to py2 behavior, so that py3 and py2 are predictable
- info.update(dict((k.lower(), v) for k, v in e.info().items()))
+ info.update({k.lower(): v for k, v in e.info().items()})
except Exception:
pass
info.update({'msg': to_native(e), 'body': body, 'status': e.code})
- except urllib_error.URLError as e:
+ except urllib.error.URLError as e:
code = int(getattr(e, 'code', -1))
info.update(dict(msg="Request failed: %s" % to_native(e), status=code))
except socket.error as e:
info.update(dict(msg="Connection failure: %s" % to_native(e), status=-1))
- except httplib.BadStatusLine as e:
+ except http.client.BadStatusLine as e:
info.update(dict(msg="Connection failure: connection was closed before a valid response was received: %s" % to_native(e.line), status=-1))
except Exception as e:
info.update(dict(msg="An unknown error occurred: %s" % to_native(e), status=-1),
@@ -2075,7 +1358,7 @@ def fetch_file(module, url, data=None, headers=None, method=None,
try:
rsp, info = fetch_url(module, url, data, headers, method, use_proxy, force, last_mod_time, timeout,
unredirected_headers=unredirected_headers, decompress=decompress, ciphers=ciphers)
- if not rsp:
+ if not rsp or (rsp.code and rsp.code >= 400):
module.fail_json(msg="Failure downloading %s, %s" % (url, info['msg']))
data = rsp.read(bufsize)
while data:
diff --git a/lib/ansible/module_utils/yumdnf.py b/lib/ansible/module_utils/yumdnf.py
index 7eb9d5f..b2cbba3 100644
--- a/lib/ansible/module_utils/yumdnf.py
+++ b/lib/ansible/module_utils/yumdnf.py
@@ -9,20 +9,16 @@
# - Abhijeet Kasurde (@Akasurde)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
-import os
-import time
-import glob
from abc import ABCMeta, abstractmethod
-from ansible.module_utils.six import with_metaclass
-
yumdnf_argument_spec = dict(
argument_spec=dict(
allow_downgrade=dict(type='bool', default=False),
+ allowerasing=dict(default=False, type="bool"),
autoremove=dict(type='bool', default=False),
+ best=dict(type="bool"),
bugfix=dict(required=False, type='bool', default=False),
cacheonly=dict(type='bool', default=False),
conf_file=dict(type='str'),
@@ -36,10 +32,14 @@ yumdnf_argument_spec = dict(
enablerepo=dict(type='list', elements='str', default=[]),
exclude=dict(type='list', elements='str', default=[]),
installroot=dict(type='str', default="/"),
- install_repoquery=dict(type='bool', default=True),
+ install_repoquery=dict(
+ type='bool', default=True,
+ removed_in_version='2.20', removed_from_collection='ansible.builtin',
+ ),
install_weak_deps=dict(type='bool', default=True),
list=dict(type='str'),
name=dict(type='list', elements='str', aliases=['pkg'], default=[]),
+ nobest=dict(type="bool"),
releasever=dict(default=None),
security=dict(type='bool', default=False),
skip_broken=dict(type='bool', default=False),
@@ -52,12 +52,12 @@ yumdnf_argument_spec = dict(
lock_timeout=dict(type='int', default=30),
),
required_one_of=[['name', 'list', 'update_cache']],
- mutually_exclusive=[['name', 'list']],
+ mutually_exclusive=[['name', 'list'], ['best', 'nobest']],
supports_check_mode=True,
)
-class YumDnf(with_metaclass(ABCMeta, object)): # type: ignore[misc]
+class YumDnf(metaclass=ABCMeta):
"""
Abstract class that handles the population of instance variables that should
be identical between both YUM and DNF modules because of the feature parity
@@ -69,7 +69,9 @@ class YumDnf(with_metaclass(ABCMeta, object)): # type: ignore[misc]
self.module = module
self.allow_downgrade = self.module.params['allow_downgrade']
+ self.allowerasing = self.module.params['allowerasing']
self.autoremove = self.module.params['autoremove']
+ self.best = self.module.params['best']
self.bugfix = self.module.params['bugfix']
self.cacheonly = self.module.params['cacheonly']
self.conf_file = self.module.params['conf_file']
@@ -87,6 +89,7 @@ class YumDnf(with_metaclass(ABCMeta, object)): # type: ignore[misc]
self.install_weak_deps = self.module.params['install_weak_deps']
self.list = self.module.params['list']
self.names = [p.strip() for p in self.module.params['name']]
+ self.nobest = self.module.params['nobest']
self.releasever = self.module.params['releasever']
self.security = self.module.params['security']
self.skip_broken = self.module.params['skip_broken']
@@ -127,31 +130,6 @@ class YumDnf(with_metaclass(ABCMeta, object)): # type: ignore[misc]
results=[],
)
- # This should really be redefined by both the yum and dnf module but a
- # default isn't a bad idea
- self.lockfile = '/var/run/yum.pid'
-
- @abstractmethod
- def is_lockfile_pid_valid(self):
- return
-
- def _is_lockfile_present(self):
- return (os.path.isfile(self.lockfile) or glob.glob(self.lockfile)) and self.is_lockfile_pid_valid()
-
- def wait_for_lock(self):
- '''Poll until the lock is removed if timeout is a positive number'''
-
- if not self._is_lockfile_present():
- return
-
- if self.lock_timeout > 0:
- for iteration in range(0, self.lock_timeout):
- time.sleep(1)
- if not self._is_lockfile_present():
- return
-
- self.module.fail_json(msg='{0} lockfile is held by another process'.format(self.pkg_mgr_name))
-
def listify_comma_sep_strings_in_list(self, some_list):
"""
method to accept a list of strings as the parameter, find any strings