diff options
Diffstat (limited to 'lib/ansible/plugins')
170 files changed, 689 insertions, 742 deletions
diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index 0333361..c083dee 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -17,9 +17,7 @@ # 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 from abc import ABC @@ -93,7 +91,7 @@ class AnsiblePlugin(ABC): return options def set_option(self, option, value): - self._options[option] = value + self._options[option] = C.config.get_config_value(option, plugin_type=self.plugin_type, plugin_name=self._load_name, direct={option: value}) def set_options(self, task_keys=None, var_options=None, direct=None): ''' @@ -108,7 +106,8 @@ class AnsiblePlugin(ABC): # allow extras/wildcards from vars that are not directly consumed in configuration # this is needed to support things like winrm that can have extended protocol options we don't directly handle if self.allow_extras and var_options and '_extras' in var_options: - self.set_option('_extras', var_options['_extras']) + # these are largely unvalidated passthroughs, either plugin or underlying API will validate + self._options['_extras'] = var_options['_extras'] def has_option(self, option): if not self._options: diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 5ba3bd7..7ebfd13 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -3,9 +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) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import base64 import json @@ -102,7 +100,7 @@ class ActionBase(ABC): etc) associated with this task. :returns: dictionary of results from the module - Implementors of action modules may find the following variables especially useful: + Implementers of action modules may find the following variables especially useful: * Module parameters. These are stored in self._task.args """ @@ -117,11 +115,9 @@ class ActionBase(ABC): del tmp if self._task.async_val and not self._supports_async: - raise AnsibleActionFail('async is not supported for this task.') + raise AnsibleActionFail('This action (%s) does not support async.' % self._task.action) elif self._task.check_mode and not self._supports_check_mode: - raise AnsibleActionSkip('check mode is not supported for this task.') - elif self._task.async_val and self._task.check_mode: - raise AnsibleActionFail('check mode and async cannot be used on same task.') + raise AnsibleActionSkip('This action (%s) does not support check mode.' % self._task.action) # Error if invalid argument is passed if self._VALID_ARGS: @@ -851,10 +847,13 @@ class ActionBase(ABC): path=path, follow=follow, get_checksum=checksum, + get_size=False, # ansible.windows.win_stat added this in 1.11.0 checksum_algorithm='sha1', ) + # Unknown opts are ignored as module_args could be specific for the + # module that is being executed. mystat = self._execute_module(module_name='ansible.legacy.stat', module_args=module_args, task_vars=all_vars, - wrap_async=False) + wrap_async=False, ignore_unknown_opts=True) if mystat.get('failed'): msg = mystat.get('module_stderr') @@ -938,7 +937,7 @@ class ActionBase(ABC): data = re.sub(r'^((\r)?\n)?BECOME-SUCCESS.*(\r)?\n', '', data) return data - def _update_module_args(self, module_name, module_args, task_vars): + def _update_module_args(self, module_name, module_args, task_vars, ignore_unknown_opts: bool = False): # set check mode in the module arguments, if required if self._task.check_mode: @@ -996,7 +995,14 @@ class ActionBase(ABC): # make sure the remote_tmp value is sent through in case modules needs to create their own module_args['_ansible_remote_tmp'] = self.get_shell_option('remote_tmp', default='~/.ansible/tmp') - def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=None, wrap_async=False): + # tells the module to ignore options that are not in its argspec. + module_args['_ansible_ignore_unknown_opts'] = ignore_unknown_opts + + # allow user to insert string to add context to remote loggging + module_args['_ansible_target_log_info'] = C.config.get_config_value('TARGET_LOG_INFO', variables=task_vars) + + def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=None, wrap_async=False, + ignore_unknown_opts: bool = False): ''' Transfer and run a module along with its arguments. ''' @@ -1032,7 +1038,7 @@ class ActionBase(ABC): if module_args is None: module_args = self._task.args - self._update_module_args(module_name, module_args, task_vars) + self._update_module_args(module_name, module_args, task_vars, ignore_unknown_opts=ignore_unknown_opts) remove_async_dir = None if wrap_async or self._task.async_val: @@ -1157,7 +1163,7 @@ class ActionBase(ABC): if data.pop("_ansible_suppress_tmpdir_delete", False): self._cleanup_remote_tmp = False - # NOTE: yum returns results .. but that made it 'compatible' with squashing, so we allow mappings, for now + # NOTE: dnf returns results .. but that made it 'compatible' with squashing, so we allow mappings, for now if 'results' in data and (not isinstance(data['results'], Sequence) or isinstance(data['results'], string_types)): data['ansible_module_results'] = data['results'] del data['results'] @@ -1339,7 +1345,7 @@ class ActionBase(ABC): display.debug(u"_low_level_execute_command() done: rc=%d, stdout=%s, stderr=%s" % (rc, out, err)) return dict(rc=rc, stdout=out, stdout_lines=out.splitlines(), stderr=err, stderr_lines=err.splitlines()) - def _get_diff_data(self, destination, source, task_vars, content, source_file=True): + def _get_diff_data(self, destination, source, task_vars, content=None, source_file=True): # Note: Since we do not diff the source and destination before we transform from bytes into # text the diff between source and destination may not be accurate. To fix this, we'd need diff --git a/lib/ansible/plugins/action/add_host.py b/lib/ansible/plugins/action/add_host.py index ede2e05..9039e34 100644 --- a/lib/ansible/plugins/action/add_host.py +++ b/lib/ansible/plugins/action/add_host.py @@ -16,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations from collections.abc import Mapping @@ -51,7 +49,7 @@ class ActionModule(ActionBase): # TODO: create 'conflict' detection in base class to deal with repeats and aliases and warn user args = combine_vars(raw, args) else: - raise AnsibleActionFail('Invalid raw parameters passed, requires a dictonary/mapping got a %s' % type(raw)) + raise AnsibleActionFail('Invalid raw parameters passed, requires a dictionary/mapping got a %s' % type(raw)) # Parse out any hostname:port patterns new_name = args.get('name', args.get('hostname', args.get('host', None))) diff --git a/lib/ansible/plugins/action/assemble.py b/lib/ansible/plugins/action/assemble.py index da794ed..6d0634c 100644 --- a/lib/ansible/plugins/action/assemble.py +++ b/lib/ansible/plugins/action/assemble.py @@ -16,8 +16,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import codecs import os @@ -140,7 +139,7 @@ class ActionModule(ActionBase): if path_checksum != dest_stat['checksum']: - if self._play_context.diff: + if self._task.diff: diff = self._get_diff_data(dest, path, task_vars) remote_path = self._connection._shell.join_path(self._connection._shell.tmpdir, 'src') diff --git a/lib/ansible/plugins/action/assert.py b/lib/ansible/plugins/action/assert.py index e2fe329..eb6c646 100644 --- a/lib/ansible/plugins/action/assert.py +++ b/lib/ansible/plugins/action/assert.py @@ -14,8 +14,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.errors import AnsibleError from ansible.playbook.conditional import Conditional diff --git a/lib/ansible/plugins/action/async_status.py b/lib/ansible/plugins/action/async_status.py index 4f50fe6..a0fe11e 100644 --- a/lib/ansible/plugins/action/async_status.py +++ b/lib/ansible/plugins/action/async_status.py @@ -1,8 +1,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 from ansible.plugins.action import ActionBase from ansible.utils.vars import merge_hash diff --git a/lib/ansible/plugins/action/command.py b/lib/ansible/plugins/action/command.py index 64e1a09..df4dbe9 100644 --- a/lib/ansible/plugins/action/command.py +++ b/lib/ansible/plugins/action/command.py @@ -1,8 +1,7 @@ # 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 from ansible.plugins.action import ActionBase from ansible.utils.vars import merge_hash diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index 048f98d..3799d11 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -16,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import json import os @@ -151,7 +149,7 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect new_parents.add((parent_stat.st_dev, parent_stat.st_ino)) if (dir_stats.st_dev, dir_stats.st_ino) in new_parents: - # This was a a circular symlink. So add it as + # This was a circular symlink. So add it as # a symlink r_files['symlinks'].append((os.readlink(dirpath), dest_dirpath)) else: @@ -212,7 +210,7 @@ class ActionModule(ActionBase): # NOTE: do not add to this. This should be made a generic function for action plugins. # This should also use the same argspec as the module instead of keeping it in sync. if 'invocation' not in result: - if self._play_context.no_log: + if self._task.no_log: result['invocation'] = "CENSORED: no_log is set" else: # NOTE: Should be removed in the future. For now keep this broken @@ -285,16 +283,21 @@ class ActionModule(ActionBase): if local_checksum != dest_status['checksum']: # The checksums don't match and we will change or error out. - if self._play_context.diff and not raw: + if self._task.diff and not raw: result['diff'].append(self._get_diff_data(dest_file, source_full, task_vars, content)) - if self._play_context.check_mode: + if self._task.check_mode: self._remove_tempfile_if_content_defined(content, content_tempfile) result['changed'] = True return result # Define a remote directory that we will copy the file to. - tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, 'source') + tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, '.source') + + # ensure we keep suffix for validate + suffix = os.path.splitext(dest_file)[1] + if suffix: + tmp_src += suffix remote_path = None @@ -387,7 +390,7 @@ class ActionModule(ActionBase): def _create_content_tempfile(self, content): ''' Create a tempfile containing defined content ''' - fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP) + fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP, prefix='.') f = os.fdopen(fd, 'wb') content = to_bytes(content) try: diff --git a/lib/ansible/plugins/action/debug.py b/lib/ansible/plugins/action/debug.py index 9e23c5f..579ffce 100644 --- a/lib/ansible/plugins/action/debug.py +++ b/lib/ansible/plugins/action/debug.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 from ansible.errors import AnsibleUndefinedVariable from ansible.module_utils.six import string_types diff --git a/lib/ansible/plugins/action/dnf.py b/lib/ansible/plugins/action/dnf.py index bf8ac3f..52391a4 100644 --- a/lib/ansible/plugins/action/dnf.py +++ b/lib/ansible/plugins/action/dnf.py @@ -1,5 +1,6 @@ # 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 annotations from ansible.errors import AnsibleActionFail from ansible.plugins.action import ActionBase @@ -7,10 +8,9 @@ from ansible.utils.display import Display display = Display() -VALID_BACKENDS = frozenset(("dnf", "dnf4", "dnf5")) +VALID_BACKENDS = frozenset(("yum", "yum4", "dnf", "dnf4", "dnf5")) -# FIXME mostly duplicate of the yum action plugin class ActionModule(ActionBase): TRANSFERS_FILES = False @@ -28,7 +28,7 @@ class ActionModule(ActionBase): module = self._task.args.get('use', self._task.args.get('use_backend', 'auto')) - if module == 'auto': + if module in {'yum', 'auto'}: try: if self._task.delegate_to: # if we delegate, we should use delegated host's facts module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to) @@ -56,7 +56,7 @@ class ActionModule(ActionBase): ) else: - if module == "dnf4": + if module in {"yum4", "dnf4"}: module = "dnf" # eliminate collisions with collections search while still allowing local override diff --git a/lib/ansible/plugins/action/fail.py b/lib/ansible/plugins/action/fail.py index dedfc8c..998d8a9 100644 --- a/lib/ansible/plugins/action/fail.py +++ b/lib/ansible/plugins/action/fail.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 from ansible.plugins.action import ActionBase diff --git a/lib/ansible/plugins/action/fetch.py b/lib/ansible/plugins/action/fetch.py index d057ed2..b7b6f30 100644 --- a/lib/ansible/plugins/action/fetch.py +++ b/lib/ansible/plugins/action/fetch.py @@ -14,8 +14,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 base64 @@ -42,7 +41,7 @@ class ActionModule(ActionBase): del tmp # tmp no longer has any effect try: - if self._play_context.check_mode: + if self._task.check_mode: raise AnsibleActionSkip('check mode not (yet) supported for this module') source = self._task.args.get('src', None) diff --git a/lib/ansible/plugins/action/gather_facts.py b/lib/ansible/plugins/action/gather_facts.py index 23962c8..31210ec 100644 --- a/lib/ansible/plugins/action/gather_facts.py +++ b/lib/ansible/plugins/action/gather_facts.py @@ -1,8 +1,7 @@ # 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 import time @@ -26,7 +25,7 @@ class ActionModule(ActionBase): # deal with 'setup specific arguments' if fact_module not in C._ACTION_SETUP: - # TODO: remove in favor of controller side argspec detecing valid arguments + # TODO: remove in favor of controller side argspec detecting valid arguments # network facts modules must support gather_subset try: name = self._connection.ansible_name.removeprefix('ansible.netcommon.') @@ -123,7 +122,7 @@ class ActionModule(ActionBase): mod_args = self._get_module_args(fact_module, task_vars) # if module does not handle timeout, use timeout to handle module, hijack async_val as this is what async_wrapper uses - # TODO: make this action compain about async/async settings, use parallel option instead .. or remove parallel in favor of async settings? + # TODO: make this action complain about async/async settings, use parallel option instead .. or remove parallel in favor of async settings? if timeout and 'gather_timeout' not in mod_args: self._task.async_val = int(timeout) elif async_val != 0: diff --git a/lib/ansible/plugins/action/group_by.py b/lib/ansible/plugins/action/group_by.py index e0c7023..369e89b 100644 --- a/lib/ansible/plugins/action/group_by.py +++ b/lib/ansible/plugins/action/group_by.py @@ -14,8 +14,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.plugins.action import ActionBase from ansible.module_utils.six import string_types diff --git a/lib/ansible/plugins/action/include_vars.py b/lib/ansible/plugins/action/include_vars.py index 83835b3..c32e622 100644 --- a/lib/ansible/plugins/action/include_vars.py +++ b/lib/ansible/plugins/action/include_vars.py @@ -1,8 +1,7 @@ # Copyright: (c) 2016, Allen Sanabria <asanabria@linuxdynasty.org> # 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 from os import path, walk import re diff --git a/lib/ansible/plugins/action/normal.py b/lib/ansible/plugins/action/normal.py index b2212e6..0476f9a 100644 --- a/lib/ansible/plugins/action/normal.py +++ b/lib/ansible/plugins/action/normal.py @@ -14,8 +14,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 import constants as C from ansible.plugins.action import ActionBase diff --git a/lib/ansible/plugins/action/package.py b/lib/ansible/plugins/action/package.py index 6c43659..6963b77 100644 --- a/lib/ansible/plugins/action/package.py +++ b/lib/ansible/plugins/action/package.py @@ -14,14 +14,14 @@ # # 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.errors import AnsibleAction, AnsibleActionFail from ansible.executor.module_common import get_action_args_with_defaults from ansible.module_utils.facts.system.pkg_mgr import PKG_MGRS from ansible.plugins.action import ActionBase from ansible.utils.display import Display +from ansible.utils.vars import combine_vars display = Display() @@ -39,31 +39,46 @@ class ActionModule(ActionBase): self._supports_async = True result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect module = self._task.args.get('use', 'auto') - if module == 'auto': - try: - if self._task.delegate_to: # if we delegate, we should use delegated host's facts - module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to) - else: - module = self._templar.template('{{ansible_facts.pkg_mgr}}') - except Exception: - pass # could not get it from template! - try: if module == 'auto': - facts = self._execute_module( - module_name='ansible.legacy.setup', - module_args=dict(filter='ansible_pkg_mgr', gather_subset='!all'), - task_vars=task_vars) - display.debug("Facts %s" % facts) - module = facts.get('ansible_facts', {}).get('ansible_pkg_mgr', 'auto') - - if module != 'auto': + + if self._task.delegate_to: + hosts_vars = task_vars['hostvars'][self._task.delegate_to] + tvars = combine_vars(self._task.vars, task_vars.get('delegated_vars', {})) + else: + hosts_vars = task_vars + tvars = task_vars + + # use config + module = tvars.get('ansible_package_use', None) + + if not module: + # no use, no config, get from facts + if hosts_vars.get('ansible_facts', {}).get('pkg_mgr', False): + facts = hosts_vars + pmgr = 'pkg_mgr' + else: + # we had no facts, so generate them + # very expensive step, we actually run fact gathering because we don't have facts for this host. + facts = self._execute_module( + module_name='ansible.legacy.setup', + module_args=dict(filter='ansible_pkg_mgr', gather_subset='!all'), + task_vars=task_vars, + ) + pmgr = 'ansible_pkg_mgr' + + try: + # actually get from facts + module = facts['ansible_facts'][pmgr] + except KeyError: + raise AnsibleActionFail('Could not detect a package manager. Try using the "use" option.') + + if module and module != 'auto': if not self._shared_loader_obj.module_loader.has_plugin(module): - raise AnsibleActionFail('Could not find a module for %s.' % module) + raise AnsibleActionFail('Could not find a matching action for the "%s" package manager.' % module) else: # run the 'package' module new_module_args = self._task.args.copy() diff --git a/lib/ansible/plugins/action/pause.py b/lib/ansible/plugins/action/pause.py index d306fbf..d603579 100644 --- a/lib/ansible/plugins/action/pause.py +++ b/lib/ansible/plugins/action/pause.py @@ -14,8 +14,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/plugins/action/raw.py b/lib/ansible/plugins/action/raw.py index b82ed34..ac337c0 100644 --- a/lib/ansible/plugins/action/raw.py +++ b/lib/ansible/plugins/action/raw.py @@ -14,8 +14,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.plugins.action import ActionBase @@ -33,7 +32,7 @@ class ActionModule(ActionBase): result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect - if self._play_context.check_mode: + if self._task.check_mode: # in --check mode, always skip this module execution result['skipped'] = True return result diff --git a/lib/ansible/plugins/action/reboot.py b/lib/ansible/plugins/action/reboot.py index c75fba8..3245716 100644 --- a/lib/ansible/plugins/action/reboot.py +++ b/lib/ansible/plugins/action/reboot.py @@ -2,8 +2,7 @@ # Copyright: (c) 2018, Sam Doran <sdoran@redhat.com> # 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 random import time @@ -241,9 +240,13 @@ class ActionModule(ActionBase): try: display.debug("{action}: setting connect_timeout to {value}".format(action=self._task.action, value=connect_timeout)) self._connection.set_option("connection_timeout", connect_timeout) - self._connection.reset() - except AttributeError: - display.warning("Connection plugin does not allow the connection timeout to be overridden") + except AnsibleError: + try: + self._connection.set_option("timeout", connect_timeout) + except (AnsibleError, AttributeError): + display.warning("Connection plugin does not allow the connection timeout to be overridden") + + self._connection.reset() # try and get boot time try: @@ -373,17 +376,25 @@ class ActionModule(ActionBase): try: connect_timeout = self._connection.get_option('connection_timeout') except KeyError: - pass + try: + connect_timeout = self._connection.get_option('timeout') + except KeyError: + pass else: if original_connection_timeout != connect_timeout: try: - display.debug("{action}: setting connect_timeout back to original value of {value}".format( - action=self._task.action, - value=original_connection_timeout)) - self._connection.set_option("connection_timeout", original_connection_timeout) + display.debug("{action}: setting connect_timeout/timeout back to original value of {value}".format(action=self._task.action, + value=original_connection_timeout)) + try: + self._connection.set_option("connection_timeout", original_connection_timeout) + except AnsibleError: + try: + self._connection.set_option("timeout", original_connection_timeout) + except AnsibleError: + raise + # reset the connection to clear the custom connection timeout self._connection.reset() except (AnsibleError, AttributeError) as e: - # reset the connection to clear the custom connection timeout display.debug("{action}: failed to reset connection_timeout back to default: {error}".format(action=self._task.action, error=to_text(e))) @@ -409,14 +420,13 @@ class ActionModule(ActionBase): def run(self, tmp=None, task_vars=None): self._supports_check_mode = True - self._supports_async = True # If running with local connection, fail so we don't reboot ourselves if self._connection.transport == 'local': msg = 'Running {0} with local connection would reboot the control node.'.format(self._task.action) return {'changed': False, 'elapsed': 0, 'rebooted': False, 'failed': True, 'msg': msg} - if self._play_context.check_mode: + if self._task.check_mode: return {'changed': True, 'elapsed': 0, 'rebooted': True} if task_vars is None: @@ -442,11 +452,16 @@ class ActionModule(ActionBase): # Get the original connection_timeout option var so it can be reset after original_connection_timeout = None + + display.debug("{action}: saving original connect_timeout of {timeout}".format(action=self._task.action, timeout=original_connection_timeout)) try: original_connection_timeout = self._connection.get_option('connection_timeout') - display.debug("{action}: saving original connect_timeout of {timeout}".format(action=self._task.action, timeout=original_connection_timeout)) except KeyError: - display.debug("{action}: connect_timeout connection option has not been set".format(action=self._task.action)) + try: + original_connection_timeout = self._connection.get_option('timeout') + except KeyError: + display.debug("{action}: connect_timeout connection option has not been set".format(action=self._task.action)) + # Initiate reboot reboot_result = self.perform_reboot(task_vars, distribution) diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index e6ebd09..7830416 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -14,8 +14,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 @@ -151,11 +150,11 @@ class ActionModule(ActionBase): # like become and environment args if getattr(self._connection._shell, "_IS_WINDOWS", False): # FUTURE: use a more public method to get the exec payload - pc = self._play_context + pc = self._task exec_data = ps_manifest._create_powershell_wrapper( to_bytes(script_cmd), source, {}, env_dict, self._task.async_val, pc.become, pc.become_method, pc.become_user, - pc.become_pass, pc.become_flags, "script", task_vars, None + self._play_context.become_pass, pc.become_flags, "script", task_vars, None ) # build the necessary exec wrapper command # FUTURE: this still doesn't let script work on Windows with non-pipelined connections or diff --git a/lib/ansible/plugins/action/service.py b/lib/ansible/plugins/action/service.py index c061687..90b0780 100644 --- a/lib/ansible/plugins/action/service.py +++ b/lib/ansible/plugins/action/service.py @@ -14,8 +14,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.errors import AnsibleAction, AnsibleActionFail diff --git a/lib/ansible/plugins/action/set_fact.py b/lib/ansible/plugins/action/set_fact.py index ee3ceb2..b95ec49 100644 --- a/lib/ansible/plugins/action/set_fact.py +++ b/lib/ansible/plugins/action/set_fact.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 from ansible.errors import AnsibleActionFail from ansible.module_utils.six import string_types diff --git a/lib/ansible/plugins/action/set_stats.py b/lib/ansible/plugins/action/set_stats.py index 5c4f005..309180f 100644 --- a/lib/ansible/plugins/action/set_stats.py +++ b/lib/ansible/plugins/action/set_stats.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 from ansible.module_utils.parsing.convert_bool import boolean from ansible.plugins.action import ActionBase diff --git a/lib/ansible/plugins/action/shell.py b/lib/ansible/plugins/action/shell.py index dd4df46..1b4fbc0 100644 --- a/lib/ansible/plugins/action/shell.py +++ b/lib/ansible/plugins/action/shell.py @@ -1,8 +1,7 @@ # 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 from ansible.errors import AnsibleActionFail from ansible.plugins.action import ActionBase diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py index 4bfd967..c1cb673 100644 --- a/lib/ansible/plugins/action/template.py +++ b/lib/ansible/plugins/action/template.py @@ -2,8 +2,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 os import shutil diff --git a/lib/ansible/plugins/action/unarchive.py b/lib/ansible/plugins/action/unarchive.py index 9bce122..bcc152d 100644 --- a/lib/ansible/plugins/action/unarchive.py +++ b/lib/ansible/plugins/action/unarchive.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/plugins/action/uri.py b/lib/ansible/plugins/action/uri.py index ffd1c89..9860f26 100644 --- a/lib/ansible/plugins/action/uri.py +++ b/lib/ansible/plugins/action/uri.py @@ -3,9 +3,7 @@ # (c) 2018, Matt Martz <matt@sivel.net> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import os @@ -22,6 +20,7 @@ class ActionModule(ActionBase): def run(self, tmp=None, task_vars=None): self._supports_async = True + self._supports_check_mode = False if task_vars is None: task_vars = dict() diff --git a/lib/ansible/plugins/action/validate_argument_spec.py b/lib/ansible/plugins/action/validate_argument_spec.py index b2c1d7b..4d68067 100644 --- a/lib/ansible/plugins/action/validate_argument_spec.py +++ b/lib/ansible/plugins/action/validate_argument_spec.py @@ -1,8 +1,7 @@ # Copyright 2021 Red Hat # 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 from ansible.errors import AnsibleError from ansible.plugins.action import ActionBase diff --git a/lib/ansible/plugins/action/wait_for_connection.py b/lib/ansible/plugins/action/wait_for_connection.py index df549d9..9eb3fac 100644 --- a/lib/ansible/plugins/action/wait_for_connection.py +++ b/lib/ansible/plugins/action/wait_for_connection.py @@ -16,8 +16,7 @@ # along with Ansible. If not, see <http://www.gnu.org/licenses/>. # CI-required python3 boilerplate -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import time from datetime import datetime, timedelta, timezone @@ -69,7 +68,7 @@ class ActionModule(ActionBase): sleep = int(self._task.args.get('sleep', self.DEFAULT_SLEEP)) timeout = int(self._task.args.get('timeout', self.DEFAULT_TIMEOUT)) - if self._play_context.check_mode: + if self._task.check_mode: display.vvv("wait_for_connection: skipping for check_mode") return dict(skipped=True) diff --git a/lib/ansible/plugins/action/yum.py b/lib/ansible/plugins/action/yum.py deleted file mode 100644 index 9121e81..0000000 --- a/lib/ansible/plugins/action/yum.py +++ /dev/null @@ -1,111 +0,0 @@ -# (c) 2018, Ansible Project -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# 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 ansible.errors import AnsibleActionFail -from ansible.plugins.action import ActionBase -from ansible.utils.display import Display - -display = Display() - -VALID_BACKENDS = frozenset(('yum', 'yum4', 'dnf', 'dnf4', 'dnf5')) - - -class ActionModule(ActionBase): - - TRANSFERS_FILES = False - - def run(self, tmp=None, task_vars=None): - ''' - Action plugin handler for yum3 vs yum4(dnf) operations. - - Enables the yum module to use yum3 and/or yum4. Yum4 is a yum - command-line compatibility layer on top of dnf. Since the Ansible - modules for yum(aka yum3) and dnf(aka yum4) call each of yum3 and yum4's - python APIs natively on the backend, we need to handle this here and - pass off to the correct Ansible module to execute on the remote system. - ''' - - self._supports_check_mode = True - self._supports_async = True - - result = super(ActionModule, self).run(tmp, task_vars) - del tmp # tmp no longer has any effect - - # Carry-over concept from the package action plugin - if 'use' in self._task.args and 'use_backend' in self._task.args: - raise AnsibleActionFail("parameters are mutually exclusive: ('use', 'use_backend')") - - module = self._task.args.get('use', self._task.args.get('use_backend', 'auto')) - - if module == 'dnf': - module = 'auto' - - if module == 'auto': - try: - if self._task.delegate_to: # if we delegate, we should use delegated host's facts - module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to) - else: - module = self._templar.template("{{ansible_facts.pkg_mgr}}") - except Exception: - pass # could not get it from template! - - if module not in VALID_BACKENDS: - facts = self._execute_module( - module_name="ansible.legacy.setup", module_args=dict(filter="ansible_pkg_mgr", gather_subset="!all"), - task_vars=task_vars) - display.debug("Facts %s" % facts) - module = facts.get("ansible_facts", {}).get("ansible_pkg_mgr", "auto") - if (not self._task.delegate_to or self._task.delegate_facts) and module != 'auto': - result['ansible_facts'] = {'pkg_mgr': module} - - if module not in VALID_BACKENDS: - result.update( - { - 'failed': True, - 'msg': ("Could not detect which major revision of yum is in use, which is required to determine module backend.", - "You should manually specify use_backend to tell the module whether to use the yum (yum3) or dnf (yum4) backend})"), - } - ) - - else: - if module in {"yum4", "dnf4"}: - module = "dnf" - - # eliminate collisions with collections search while still allowing local override - module = 'ansible.legacy.' + module - - if not self._shared_loader_obj.module_loader.has_plugin(module): - result.update({'failed': True, 'msg': "Could not find a yum module backend for %s." % module}) - else: - new_module_args = self._task.args.copy() - if 'use_backend' in new_module_args: - del new_module_args['use_backend'] - if 'use' in new_module_args: - del new_module_args['use'] - - display.vvvv("Running %s as the backend for the yum action plugin" % module) - result.update(self._execute_module( - module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)) - - # Cleanup - if not self._task.async_val: - # remove a temporary path we created - self._remove_tmp_path(self._connection._shell.tmpdir) - - return result diff --git a/lib/ansible/plugins/become/__init__.py b/lib/ansible/plugins/become/__init__.py index 0e4a411..0ac1512 100644 --- a/lib/ansible/plugins/become/__init__.py +++ b/lib/ansible/plugins/become/__init__.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # 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 shlex diff --git a/lib/ansible/plugins/become/runas.py b/lib/ansible/plugins/become/runas.py index 0b7d466..3094c46 100644 --- a/lib/ansible/plugins/become/runas.py +++ b/lib/ansible/plugins/become/runas.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # 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 DOCUMENTATION = """ name: runas diff --git a/lib/ansible/plugins/become/su.py b/lib/ansible/plugins/become/su.py index 7fa5413..ae2d39a 100644 --- a/lib/ansible/plugins/become/su.py +++ b/lib/ansible/plugins/become/su.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # 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 DOCUMENTATION = """ name: su diff --git a/lib/ansible/plugins/become/sudo.py b/lib/ansible/plugins/become/sudo.py index fb285f0..6a33c98 100644 --- a/lib/ansible/plugins/become/sudo.py +++ b/lib/ansible/plugins/become/sudo.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # 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 DOCUMENTATION = """ name: sudo diff --git a/lib/ansible/plugins/cache/__init__.py b/lib/ansible/plugins/cache/__init__.py index 24f4e77..3bc5a16 100644 --- a/lib/ansible/plugins/cache/__init__.py +++ b/lib/ansible/plugins/cache/__init__.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 copy import errno @@ -29,6 +28,7 @@ from collections.abc import MutableMapping from ansible import constants as C from ansible.errors import AnsibleError +from ansible.module_utils.common.file import S_IRWU_RG_RO from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.plugins import AnsiblePlugin from ansible.plugins.loader import cache_loader @@ -165,7 +165,7 @@ class BaseFileCacheModule(BaseCacheModule): display.warning("error in '%s' cache plugin while trying to write to '%s' : %s" % (self.plugin_name, tmpfile_path, to_bytes(e))) try: os.rename(tmpfile_path, cachefile) - os.chmod(cachefile, mode=0o644) + os.chmod(cachefile, mode=S_IRWU_RG_RO) except (OSError, IOError) as e: display.warning("error in '%s' cache plugin while trying to move '%s' to '%s' : %s" % (self.plugin_name, tmpfile_path, cachefile, to_bytes(e))) finally: diff --git a/lib/ansible/plugins/cache/base.py b/lib/ansible/plugins/cache/base.py index a947eb7..a7c7468 100644 --- a/lib/ansible/plugins/cache/base.py +++ b/lib/ansible/plugins/cache/base.py @@ -14,8 +14,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 # moved actual classes to __init__ kept here for backward compat with 3rd parties from ansible.plugins.cache import BaseCacheModule, BaseFileCacheModule # pylint: disable=unused-import diff --git a/lib/ansible/plugins/cache/jsonfile.py b/lib/ansible/plugins/cache/jsonfile.py index a26828a..69fd828 100644 --- a/lib/ansible/plugins/cache/jsonfile.py +++ b/lib/ansible/plugins/cache/jsonfile.py @@ -2,9 +2,7 @@ # (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = ''' name: jsonfile diff --git a/lib/ansible/plugins/cache/memory.py b/lib/ansible/plugins/cache/memory.py index 59f97b6..59991ac 100644 --- a/lib/ansible/plugins/cache/memory.py +++ b/lib/ansible/plugins/cache/memory.py @@ -3,8 +3,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 DOCUMENTATION = ''' name: memory diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py index 4346958..8f9b25e 100644 --- a/lib/ansible/plugins/callback/__init__.py +++ b/lib/ansible/plugins/callback/__init__.py @@ -15,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import difflib import json @@ -169,7 +167,7 @@ class CallbackBase(AnsiblePlugin): _copy_result = deepcopy def set_option(self, k, v): - self._plugin_options[k] = v + self._plugin_options[k] = C.config.get_config_value(k, plugin_type=self.plugin_type, plugin_name=self._load_name, direct={k: v}) def get_option(self, k): return self._plugin_options[k] diff --git a/lib/ansible/plugins/callback/default.py b/lib/ansible/plugins/callback/default.py index 54ef452..c96d9ab 100644 --- a/lib/ansible/plugins/callback/default.py +++ b/lib/ansible/plugins/callback/default.py @@ -2,8 +2,7 @@ # (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 DOCUMENTATION = ''' name: default @@ -166,7 +165,7 @@ class CallbackModule(CallbackBase): # args can be specified as no_log in several places: in the task or in # the argument spec. We can check whether the task is no_log but the # argument spec can't be because that is only run on the target - # machine and we haven't run it thereyet at this time. + # machine and we haven't run it there yet at this time. # # So we give people a config option to affect display of the args so # that they can secure this if they feel that their stdout is insecure diff --git a/lib/ansible/plugins/callback/junit.py b/lib/ansible/plugins/callback/junit.py index 92158ef..73db9d5 100644 --- a/lib/ansible/plugins/callback/junit.py +++ b/lib/ansible/plugins/callback/junit.py @@ -2,8 +2,7 @@ # (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 DOCUMENTATION = ''' name: junit diff --git a/lib/ansible/plugins/callback/minimal.py b/lib/ansible/plugins/callback/minimal.py index c4d713f..e316d8f 100644 --- a/lib/ansible/plugins/callback/minimal.py +++ b/lib/ansible/plugins/callback/minimal.py @@ -2,9 +2,7 @@ # (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = ''' name: minimal diff --git a/lib/ansible/plugins/callback/oneline.py b/lib/ansible/plugins/callback/oneline.py index 556f21c..3a5eb72 100644 --- a/lib/ansible/plugins/callback/oneline.py +++ b/lib/ansible/plugins/callback/oneline.py @@ -2,9 +2,7 @@ # (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = ''' name: oneline diff --git a/lib/ansible/plugins/callback/tree.py b/lib/ansible/plugins/callback/tree.py index 52a5fee..b7f85f0 100644 --- a/lib/ansible/plugins/callback/tree.py +++ b/lib/ansible/plugins/callback/tree.py @@ -2,8 +2,7 @@ # (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 DOCUMENTATION = ''' name: tree diff --git a/lib/ansible/plugins/cliconf/__init__.py b/lib/ansible/plugins/cliconf/__init__.py index 3201057..9befd36 100644 --- a/lib/ansible/plugins/cliconf/__init__.py +++ b/lib/ansible/plugins/cliconf/__init__.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 from abc import abstractmethod from functools import wraps diff --git a/lib/ansible/plugins/connection/__init__.py b/lib/ansible/plugins/connection/__init__.py index 5f7e282..e769770 100644 --- a/lib/ansible/plugins/connection/__init__.py +++ b/lib/ansible/plugins/connection/__init__.py @@ -2,8 +2,7 @@ # (c) 2015 Toshio Kuratomi <tkuratomi@ansible.com> # (c) 2017, Peter Sprygada <psprygad@redhat.com> # (c) 2017 Ansible Project -from __future__ import (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import collections.abc as c import fcntl @@ -267,19 +266,19 @@ class ConnectionBase(AnsiblePlugin): # its my cousin ... value = self._shell._load_name else: - # deal with generic options if the plugin supports em (for exmaple not all connections have a remote user) + # deal with generic options if the plugin supports em (for example not all connections have a remote user) options = C.config.get_plugin_options_from_var('connection', self._load_name, varname) if options: value = self.get_option(options[0]) # for these variables there should be only one option elif 'become' not in varname: - # fallback to play_context, unles becoem related TODO: in the end should come from task/play and not pc + # fallback to play_context, unless become related TODO: in the end, should come from task/play and not pc for prop, var_list in C.MAGIC_VARIABLE_MAPPING.items(): if varname in var_list: try: value = getattr(self._play_context, prop) break except AttributeError: - # it was not defined, fine to ignore + # It was not defined; fine to ignore continue if value is not None: diff --git a/lib/ansible/plugins/connection/local.py b/lib/ansible/plugins/connection/local.py index d6dccc7..464d8e8 100644 --- a/lib/ansible/plugins/connection/local.py +++ b/lib/ansible/plugins/connection/local.py @@ -2,8 +2,7 @@ # (c) 2015, 2017 Toshio Kuratomi <tkuratomi@ansible.com> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -from __future__ import (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = ''' name: local @@ -22,13 +21,13 @@ import fcntl import getpass import os import pty +import selectors import shutil import subprocess import typing as t import ansible.constants as C from ansible.errors import AnsibleError, AnsibleFileNotFound -from ansible.module_utils.compat import selectors from ansible.module_utils.six import text_type, binary_type from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.plugins.connection import ConnectionBase @@ -90,7 +89,7 @@ class Connection(ConnectionBase): master = None stdin = subprocess.PIPE if sudoable and self.become and self.become.expect_prompt() and not self.get_option('pipelining'): - # Create a pty if sudoable for privlege escalation that needs it. + # Create a pty if sudoable for privilege escalation that needs it. # Falls back to using a standard pipe if this fails, which may # cause the command to fail in certain situations where we are escalating # privileges or the command otherwise needs a pty. diff --git a/lib/ansible/plugins/connection/paramiko_ssh.py b/lib/ansible/plugins/connection/paramiko_ssh.py index 172dbda..924208b 100644 --- a/lib/ansible/plugins/connection/paramiko_ssh.py +++ b/lib/ansible/plugins/connection/paramiko_ssh.py @@ -1,8 +1,7 @@ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (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 (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = """ author: Ansible Core Team diff --git a/lib/ansible/plugins/connection/psrp.py b/lib/ansible/plugins/connection/psrp.py index 37a4694..b69a1d8 100644 --- a/lib/ansible/plugins/connection/psrp.py +++ b/lib/ansible/plugins/connection/psrp.py @@ -1,8 +1,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 (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = """ author: Ansible Core Team @@ -14,7 +13,7 @@ description: underlying transport but instead runs in a PowerShell interpreter. version_added: "2.7" requirements: -- pypsrp>=0.4.0 (Python library) +- pypsrp>=0.4.0, <1.0.0 (Python library) extends_documentation_fragment: - connection_pipelining options: diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py index 49b2ed2..5c4c28d 100644 --- a/lib/ansible/plugins/connection/ssh.py +++ b/lib/ansible/plugins/connection/ssh.py @@ -4,8 +4,7 @@ # 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 (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = ''' name: ssh @@ -20,6 +19,8 @@ DOCUMENTATION = ''' - connection_pipelining version_added: historical notes: + - This plugin is mostly a wrapper to the ``ssh`` CLI utility and the exact behavior of the options depends on this tool. + This means that the documentation provided here is subject to be overridden by the CLI tool itself. - Many options default to V(None) here but that only means we do not override the SSH tool's defaults and/or configuration. For example, if you specify the port in this plugin it will override any C(Port) entry in your C(.ssh/config). - The ssh CLI tool uses return code 255 as a 'connection error', this can conflict with commands/tools that @@ -36,7 +37,7 @@ DOCUMENTATION = ''' - name: delegated_vars['ansible_host'] - name: delegated_vars['ansible_ssh_host'] host_key_checking: - description: Determines if SSH should check host keys. + description: Determines if SSH should reject or not a connection after checking host keys. default: True type: boolean ini: @@ -304,12 +305,13 @@ DOCUMENTATION = ''' - name: ansible_sftp_batch_mode version_added: '2.7' ssh_transfer_method: - description: - - "Preferred method to use when transferring files over ssh" - - Setting to 'smart' (default) will try them in order, until one succeeds or they all fail - - For OpenSSH >=9.0 you must add an additional option to enable scp (scp_extra_args="-O") - - Using 'piped' creates an ssh pipe with C(dd) on either side to copy the data - choices: ['sftp', 'scp', 'piped', 'smart'] + description: Preferred method to use when transferring files over ssh + choices: + sftp: This is the most reliable way to copy things with SSH. + scp: Deprecated in OpenSSH. For OpenSSH >=9.0 you must add an additional option to enable scp C(scp_extra_args="-O"). + piped: Creates an SSH pipe with C(dd) on either side to copy the data. + smart: Tries each method in order (sftp > scp > piped), until one succeeds or they all fail. + default: smart type: string env: [{name: ANSIBLE_SSH_TRANSFER_METHOD}] ini: @@ -317,24 +319,6 @@ DOCUMENTATION = ''' vars: - name: ansible_ssh_transfer_method version_added: '2.12' - scp_if_ssh: - deprecated: - why: In favor of the O(ssh_transfer_method) option. - version: "2.17" - alternatives: O(ssh_transfer_method) - default: smart - description: - - "Preferred method to use when transferring files over SSH." - - When set to V(smart), Ansible will try them until one succeeds or they all fail. - - If set to V(True), it will force 'scp', if V(False) it will use 'sftp'. - - For OpenSSH >=9.0 you must add an additional option to enable scp (C(scp_extra_args="-O")) - - This setting will overridden by O(ssh_transfer_method) if set. - env: [{name: ANSIBLE_SCP_IF_SSH}] - ini: - - {key: scp_if_ssh, section: ssh_connection} - vars: - - name: ansible_scp_if_ssh - version_added: '2.7' use_tty: version_added: '2.5' default: true @@ -389,6 +373,7 @@ import io import os import pty import re +import selectors import shlex import subprocess import time @@ -401,11 +386,8 @@ from ansible.errors import ( AnsibleError, AnsibleFileNotFound, ) -from ansible.errors import AnsibleOptionsError -from ansible.module_utils.compat import selectors from ansible.module_utils.six import PY3, text_type, binary_type from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text -from ansible.module_utils.parsing.convert_bool import BOOLEANS, boolean from ansible.plugins.connection import ConnectionBase, BUFSIZE from ansible.plugins.shell.powershell import _parse_clixml from ansible.utils.display import Display @@ -746,8 +728,8 @@ class Connection(ConnectionBase): self._add_args(b_command, b_args, u'disable batch mode for sshpass') b_command += [b'-b', b'-'] - if display.verbosity > 3: - b_command.append(b'-vvv') + if display.verbosity: + b_command.append(b'-' + (b'v' * display.verbosity)) # Next, we add ssh_args ssh_args = self.get_option('ssh_args') @@ -1240,31 +1222,13 @@ class Connection(ConnectionBase): # Transfer methods to try methods = [] - # Use the transfer_method option if set, otherwise use scp_if_ssh + # Use the transfer_method option if set ssh_transfer_method = self.get_option('ssh_transfer_method') - scp_if_ssh = self.get_option('scp_if_ssh') - if ssh_transfer_method is None and scp_if_ssh == 'smart': - ssh_transfer_method = 'smart' - if ssh_transfer_method is not None: - if ssh_transfer_method == 'smart': - methods = smart_methods - else: - methods = [ssh_transfer_method] + if ssh_transfer_method == 'smart': + methods = smart_methods else: - # since this can be a non-bool now, we need to handle it correctly - if not isinstance(scp_if_ssh, bool): - scp_if_ssh = scp_if_ssh.lower() - if scp_if_ssh in BOOLEANS: - scp_if_ssh = boolean(scp_if_ssh, strict=False) - elif scp_if_ssh != 'smart': - raise AnsibleOptionsError('scp_if_ssh needs to be one of [smart|True|False]') - if scp_if_ssh == 'smart': - methods = smart_methods - elif scp_if_ssh is True: - methods = ['scp'] - else: - methods = ['sftp'] + methods = [ssh_transfer_method] for method in methods: returncode = stdout = stderr = None diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index b297495..c6a4683 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -2,8 +2,7 @@ # 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 (annotations, absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations DOCUMENTATION = """ author: Ansible Core Team @@ -148,8 +147,8 @@ DOCUMENTATION = """ seconds higher than the WS-Man operation timeout, thus make the connection more robust on networks with long latency and/or many hops between server and client network wise. - - Setting the difference bewteen the operation and the read timeout to 10 seconds - alligns it to the defaults used in the winrm-module and the PSRP-module which also + - Setting the difference between the operation and the read timeout to 10 seconds + aligns it to the defaults used in the winrm-module and the PSRP-module which also uses 10 seconds (30 seconds for read timeout and 20 seconds for operation timeout) - Corresponds to the C(operation_timeout_sec) and C(read_timeout_sec) args in pywinrm so avoid setting these vars diff --git a/lib/ansible/plugins/doc_fragments/action_common_attributes.py b/lib/ansible/plugins/doc_fragments/action_common_attributes.py index c135df5..688d675 100644 --- a/lib/ansible/plugins/doc_fragments/action_common_attributes.py +++ b/lib/ansible/plugins/doc_fragments/action_common_attributes.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Copyright: 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 class ModuleDocFragment(object): @@ -11,7 +10,7 @@ class ModuleDocFragment(object): DOCUMENTATION = r''' attributes: check_mode: - description: Can run in check_mode and return changed status prediction without modifying target + description: Can run in check_mode and return changed status prediction without modifying target, if not supported the action will be skipped. diff_mode: description: Will return details on what has changed (or possibly needs changing in check_mode), when in diff mode platform: diff --git a/lib/ansible/plugins/doc_fragments/action_core.py b/lib/ansible/plugins/doc_fragments/action_core.py index 931ca14..56214b7 100644 --- a/lib/ansible/plugins/doc_fragments/action_core.py +++ b/lib/ansible/plugins/doc_fragments/action_core.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- # 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 -# WARNING: this is mostly here as a convinence for documenting core behaviours, no plugin outside of ansible-core should use this file +# WARNING: this is mostly here as a convenience for documenting core behaviours, no plugin outside of ansible-core should use this file class ModuleDocFragment(object): # requires action_common @@ -29,7 +28,7 @@ attributes: support: full platforms: all until: - description: Denotes if this action objeys until/retry/poll keywords + description: Denotes if this action obeys until/retry/poll keywords support: full tags: description: Allows for the 'tags' keyword to control the selection of this action for execution diff --git a/lib/ansible/plugins/doc_fragments/backup.py b/lib/ansible/plugins/doc_fragments/backup.py index d2e76dc..037df24 100644 --- a/lib/ansible/plugins/doc_fragments/backup.py +++ b/lib/ansible/plugins/doc_fragments/backup.py @@ -2,8 +2,7 @@ # Copyright: (c) 2015, Ansible, Inc # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/connection_pipelining.py b/lib/ansible/plugins/doc_fragments/connection_pipelining.py index fa18265..a590be3 100644 --- a/lib/ansible/plugins/doc_fragments/connection_pipelining.py +++ b/lib/ansible/plugins/doc_fragments/connection_pipelining.py @@ -1,7 +1,6 @@ # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/constructed.py b/lib/ansible/plugins/doc_fragments/constructed.py index 8e45043..c5d7e0a 100644 --- a/lib/ansible/plugins/doc_fragments/constructed.py +++ b/lib/ansible/plugins/doc_fragments/constructed.py @@ -2,8 +2,7 @@ # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/decrypt.py b/lib/ansible/plugins/doc_fragments/decrypt.py index ea7cf59..c2da1cf 100644 --- a/lib/ansible/plugins/doc_fragments/decrypt.py +++ b/lib/ansible/plugins/doc_fragments/decrypt.py @@ -2,8 +2,7 @@ # Copyright: (c) 2017, Brian Coca <bcoca@redhat.com> # 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 class ModuleDocFragment(object): @@ -13,7 +12,7 @@ class ModuleDocFragment(object): options: decrypt: description: - - This option controls the autodecryption of source files using vault. + - This option controls the auto-decryption of source files using vault. type: bool default: yes version_added: '2.4' diff --git a/lib/ansible/plugins/doc_fragments/default_callback.py b/lib/ansible/plugins/doc_fragments/default_callback.py index 5798334..e206eb3 100644 --- a/lib/ansible/plugins/doc_fragments/default_callback.py +++ b/lib/ansible/plugins/doc_fragments/default_callback.py @@ -2,8 +2,7 @@ # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/files.py b/lib/ansible/plugins/doc_fragments/files.py index 3741652..ec76267 100644 --- a/lib/ansible/plugins/doc_fragments/files.py +++ b/lib/ansible/plugins/doc_fragments/files.py @@ -2,8 +2,7 @@ # Copyright: (c) 2014, Matt Martz <matt@sivel.net> # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/inventory_cache.py b/lib/ansible/plugins/doc_fragments/inventory_cache.py index 1a0d631..03d6d7c 100644 --- a/lib/ansible/plugins/doc_fragments/inventory_cache.py +++ b/lib/ansible/plugins/doc_fragments/inventory_cache.py @@ -2,8 +2,7 @@ # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/result_format_callback.py b/lib/ansible/plugins/doc_fragments/result_format_callback.py index f4f82b7..3ca74aa 100644 --- a/lib/ansible/plugins/doc_fragments/result_format_callback.py +++ b/lib/ansible/plugins/doc_fragments/result_format_callback.py @@ -2,8 +2,7 @@ # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/return_common.py b/lib/ansible/plugins/doc_fragments/return_common.py index 6f54288..900e4c0 100644 --- a/lib/ansible/plugins/doc_fragments/return_common.py +++ b/lib/ansible/plugins/doc_fragments/return_common.py @@ -2,8 +2,7 @@ # Copyright: (c) 2016, Ansible, Inc # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/shell_common.py b/lib/ansible/plugins/doc_fragments/shell_common.py index 39d8730..a97fa99 100644 --- a/lib/ansible/plugins/doc_fragments/shell_common.py +++ b/lib/ansible/plugins/doc_fragments/shell_common.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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/shell_windows.py b/lib/ansible/plugins/doc_fragments/shell_windows.py index 0bcc89c..1f25ce0 100644 --- a/lib/ansible/plugins/doc_fragments/shell_windows.py +++ b/lib/ansible/plugins/doc_fragments/shell_windows.py @@ -1,7 +1,6 @@ # Copyright (c) 2019 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/template_common.py b/lib/ansible/plugins/doc_fragments/template_common.py index dbfe482..9795e43 100644 --- a/lib/ansible/plugins/doc_fragments/template_common.py +++ b/lib/ansible/plugins/doc_fragments/template_common.py @@ -3,8 +3,7 @@ # Copyright (c) 2019 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/url.py b/lib/ansible/plugins/doc_fragments/url.py index bafeded..8f90465 100644 --- a/lib/ansible/plugins/doc_fragments/url.py +++ b/lib/ansible/plugins/doc_fragments/url.py @@ -2,8 +2,7 @@ # Copyright: (c) 2018, John Barker <gundalow@redhat.com> # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/url_windows.py b/lib/ansible/plugins/doc_fragments/url_windows.py index 7b3e873..4b2c19d 100644 --- a/lib/ansible/plugins/doc_fragments/url_windows.py +++ b/lib/ansible/plugins/doc_fragments/url_windows.py @@ -3,8 +3,7 @@ # Copyright (c) 2019 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 class ModuleDocFragment: diff --git a/lib/ansible/plugins/doc_fragments/validate.py b/lib/ansible/plugins/doc_fragments/validate.py index ac66d25..b71011c 100644 --- a/lib/ansible/plugins/doc_fragments/validate.py +++ b/lib/ansible/plugins/doc_fragments/validate.py @@ -2,8 +2,7 @@ # Copyright: (c) 2015, Ansible, Inc # 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/doc_fragments/vars_plugin_staging.py b/lib/ansible/plugins/doc_fragments/vars_plugin_staging.py index eacac17..698b7be 100644 --- a/lib/ansible/plugins/doc_fragments/vars_plugin_staging.py +++ b/lib/ansible/plugins/doc_fragments/vars_plugin_staging.py @@ -3,8 +3,7 @@ # Copyright: (c) 2019, 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 class ModuleDocFragment(object): diff --git a/lib/ansible/plugins/filter/__init__.py b/lib/ansible/plugins/filter/__init__.py index 63b6602..003711f 100644 --- a/lib/ansible/plugins/filter/__init__.py +++ b/lib/ansible/plugins/filter/__init__.py @@ -1,8 +1,7 @@ # (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 from ansible import constants as C from ansible.plugins import AnsibleJinja2Plugin diff --git a/lib/ansible/plugins/filter/b64decode.yml b/lib/ansible/plugins/filter/b64decode.yml index af8045a..339de3a 100644 --- a/lib/ansible/plugins/filter/b64decode.yml +++ b/lib/ansible/plugins/filter/b64decode.yml @@ -2,28 +2,28 @@ DOCUMENTATION: name: b64decode author: ansible core team version_added: 'historical' - short_description: Decode a base64 string + short_description: Decode a Base64 string description: - Base64 decoding function. - The return value is a string. - - Trying to store a binary blob in a string most likely corrupts the binary. To base64 decode a binary blob, - use the ``base64`` command and pipe the encoded data through standard input. - For example, in the ansible.builtin.shell`` module, ``cmd="base64 --decode > myfile.bin" stdin="{{ encoded }}"``. + - Trying to store a binary blob in a string most likely corrupts the binary. To Base64 decode a binary blob, + use the I(base64) command and pipe the encoded data through standard input. + For example, in the M(ansible.builtin.shell) module, ``cmd="base64 --decode > myfile.bin" stdin="{{ encoded }}"``. positional: _input options: _input: - description: A base64 string to decode. + description: A Base64 string to decode. type: string required: true EXAMPLES: | - # b64 decode a string + # Base64 decode a string lola: "{{ 'bG9sYQ==' | b64decode }}" - # b64 decode the content of 'b64stuff' variable + # Base64 decode the content of 'b64stuff' variable stuff: "{{ b64stuff | b64decode }}" RETURN: _value: - description: The contents of the base64 encoded string. + description: The contents of the Base64 encoded string. type: string diff --git a/lib/ansible/plugins/filter/b64encode.yml b/lib/ansible/plugins/filter/b64encode.yml index 976d1fe..ed32bfb 100644 --- a/lib/ansible/plugins/filter/b64encode.yml +++ b/lib/ansible/plugins/filter/b64encode.yml @@ -2,7 +2,7 @@ DOCUMENTATION: name: b64encode author: ansible core team version_added: 'historical' - short_description: Encode a string as base64 + short_description: Encode a string as Base64 description: - Base64 encoding function. positional: _input @@ -13,13 +13,13 @@ DOCUMENTATION: required: true EXAMPLES: | - # b64 encode a string + # Base64 encode a string b64lola: "{{ 'lola'| b64encode }}" - # b64 encode the content of 'stuff' variable + # Base64 encode the content of 'stuff' variable b64stuff: "{{ stuff | b64encode }}" RETURN: _value: - description: A base64 encoded string. + description: A Base64 encoded string. type: string diff --git a/lib/ansible/plugins/filter/comment.yml b/lib/ansible/plugins/filter/comment.yml index f1e47e6..c2e4776 100644 --- a/lib/ansible/plugins/filter/comment.yml +++ b/lib/ansible/plugins/filter/comment.yml @@ -18,7 +18,7 @@ DOCUMENTATION: decoration: description: Indicator for comment or intermediate comment depending on the style. type: string - begining: + beginning: description: Indicator of the start of a comment block, only available for styles that support multiline comments. type: string end: diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index eee43e6..ef9c09f 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -1,9 +1,7 @@ # (c) 2012, Jeroen Hoekx <jeroen@hoekx.be> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import base64 import glob @@ -46,7 +44,7 @@ UUID_NAMESPACE_ANSIBLE = uuid.UUID('361E6D51-FAEC-444A-9079-341386DA8E2E') def to_yaml(a, *args, **kw): - '''Make verbose, human readable yaml''' + '''Make verbose, human-readable yaml''' default_flow_style = kw.pop('default_flow_style', None) try: transformed = yaml.dump(a, Dumper=AnsibleDumper, allow_unicode=True, default_flow_style=default_flow_style, **kw) @@ -56,7 +54,7 @@ def to_yaml(a, *args, **kw): def to_nice_yaml(a, indent=4, *args, **kw): - '''Make verbose, human readable yaml''' + '''Make verbose, human-readable yaml''' try: transformed = yaml.dump(a, Dumper=AnsibleDumper, indent=indent, allow_unicode=True, default_flow_style=False, **kw) except Exception as e: @@ -77,7 +75,7 @@ def to_json(a, *args, **kw): def to_nice_json(a, indent=4, sort_keys=True, *args, **kw): - '''Make verbose, human readable JSON''' + '''Make verbose, human-readable JSON''' return to_json(a, indent=indent, sort_keys=sort_keys, separators=(',', ': '), *args, **kw) @@ -122,7 +120,7 @@ def fileglob(pathname): return [g for g in glob.glob(pathname) if os.path.isfile(g)] -def regex_replace(value='', pattern='', replacement='', ignorecase=False, multiline=False): +def regex_replace(value='', pattern='', replacement='', ignorecase=False, multiline=False, count=0, mandatory_count=0): ''' Perform a `re.sub` returning a string ''' value = to_text(value, errors='surrogate_or_strict', nonstring='simplerepr') @@ -133,7 +131,11 @@ def regex_replace(value='', pattern='', replacement='', ignorecase=False, multil if multiline: flags |= re.M _re = re.compile(pattern, flags=flags) - return _re.sub(replacement, value) + (output, subs) = _re.subn(replacement, value, count=count) + if mandatory_count and mandatory_count != subs: + raise AnsibleFilterError("'%s' should match %d times, but matches %d times in '%s'" + % (pattern, mandatory_count, count, value)) + return output def regex_findall(value, regex, multiline=False, ignorecase=False): @@ -595,7 +597,7 @@ def commonpath(paths): :rtype: str """ if not is_sequence(paths): - raise AnsibleFilterTypeError("|path_join expects sequence, got %s instead." % type(paths)) + raise AnsibleFilterTypeError("|commonpath expects sequence, got %s instead." % type(paths)) return os.path.commonpath(paths) diff --git a/lib/ansible/plugins/filter/encryption.py b/lib/ansible/plugins/filter/encryption.py index d501879..c6863fd 100644 --- a/lib/ansible/plugins/filter/encryption.py +++ b/lib/ansible/plugins/filter/encryption.py @@ -1,8 +1,6 @@ # Copyright: (c) 2021, Ansible Project -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations from jinja2.runtime import Undefined from jinja2.exceptions import UndefinedError diff --git a/lib/ansible/plugins/filter/extract.yml b/lib/ansible/plugins/filter/extract.yml index a7c4e91..da1f4c0 100644 --- a/lib/ansible/plugins/filter/extract.yml +++ b/lib/ansible/plugins/filter/extract.yml @@ -17,7 +17,7 @@ DOCUMENTATION: type: raw required: true morekeys: - description: Indicies or keys to extract from the initial result (subkeys/subindices). + description: Indices or keys to extract from the initial result (subkeys/subindices). type: list elements: dictionary required: true diff --git a/lib/ansible/plugins/filter/from_yaml_all.yml b/lib/ansible/plugins/filter/from_yaml_all.yml index c3dd1f6..f01edbf 100644 --- a/lib/ansible/plugins/filter/from_yaml_all.yml +++ b/lib/ansible/plugins/filter/from_yaml_all.yml @@ -5,7 +5,7 @@ DOCUMENTATION: description: - Converts a YAML documents in a string representation into an equivalent structured Ansible variable. - Ansible internally auto-converts YAML strings into variable structures in most contexts, but by default does not handle 'multi document' YAML files or strings. - - If multiple YAML documents are not supplied, this is the equivalend of using C(from_yaml). + - If multiple YAML documents are not supplied, this is the equivalence of using C(from_yaml). notes: - This filter functions as a wrapper to the Python C(yaml.safe_load_all) function, part of the L(pyyaml Python library, https://pypi.org/project/PyYAML/). - Possible conflicts in variable names from the multiple documents are resolved directly by the pyyaml library. diff --git a/lib/ansible/plugins/filter/human_readable.yml b/lib/ansible/plugins/filter/human_readable.yml index 2c331b7..b5dd364 100644 --- a/lib/ansible/plugins/filter/human_readable.yml +++ b/lib/ansible/plugins/filter/human_readable.yml @@ -1,9 +1,9 @@ DOCUMENTATION: name: human_redable version_added: "historical" - short_description: Make bytes/bits human readable + short_description: Make bytes/bits human-readable description: - - Convert byte or bit figures to more human readable formats. + - Convert byte or bit figures to more human-readable formats. positional: _input, isbits, unit options: _input: @@ -31,5 +31,5 @@ EXAMPLES: | RETURN: _value: - description: Human readable byte or bit size. + description: human-readable byte or bit size. type: str diff --git a/lib/ansible/plugins/filter/human_to_bytes.yml b/lib/ansible/plugins/filter/human_to_bytes.yml index c861350..2739129 100644 --- a/lib/ansible/plugins/filter/human_to_bytes.yml +++ b/lib/ansible/plugins/filter/human_to_bytes.yml @@ -3,11 +3,11 @@ DOCUMENTATION: version_added: "historical" short_description: Get bytes from string description: - - Convert a human readable byte or bit string into a number bytes. + - Convert a human-readable byte or bit string into a number bytes. positional: _input, default_unit, isbits options: _input: - description: Human readable description of a number of bytes. + description: human-readable description of a number of bytes. type: int required: true default_unit: diff --git a/lib/ansible/plugins/filter/mandatory.yml b/lib/ansible/plugins/filter/mandatory.yml index 1405884..ed3a7dd 100644 --- a/lib/ansible/plugins/filter/mandatory.yml +++ b/lib/ansible/plugins/filter/mandatory.yml @@ -1,7 +1,7 @@ DOCUMENTATION: name: mandatory version_added: "historical" - short_description: make a variable's existance mandatory + short_description: make a variable's existence mandatory description: - Depending on context undefined variables can be ignored or skipped, this ensures they force an error. positional: _input diff --git a/lib/ansible/plugins/filter/mathstuff.py b/lib/ansible/plugins/filter/mathstuff.py index 4ff1118..9772cb5 100644 --- a/lib/ansible/plugins/filter/mathstuff.py +++ b/lib/ansible/plugins/filter/mathstuff.py @@ -145,7 +145,7 @@ def inversepower(x, base=2): def human_readable(size, isbits=False, unit=None): - ''' Return a human readable string ''' + ''' Return a human-readable string ''' try: return formatters.bytes_to_human(size, isbits, unit) except TypeError as e: @@ -155,7 +155,7 @@ def human_readable(size, isbits=False, unit=None): def human_to_bytes(size, default_unit=None, isbits=False): - ''' Return bytes count from a human readable string ''' + ''' Return bytes count from a human-readable string ''' try: return formatters.human_to_bytes(size, default_unit, isbits) except TypeError as e: diff --git a/lib/ansible/plugins/filter/password_hash.yml b/lib/ansible/plugins/filter/password_hash.yml index d12efb4..a9516b7 100644 --- a/lib/ansible/plugins/filter/password_hash.yml +++ b/lib/ansible/plugins/filter/password_hash.yml @@ -7,6 +7,7 @@ DOCUMENTATION: positional: _input notes: - Algorithms available might be restricted by the system. + - Algorithms may restrict salt length or content. For example, Blowfish/bcrypt requires a 22-character salt. options: _input: description: Secret to hash. @@ -18,8 +19,8 @@ DOCUMENTATION: default: sha512 choices: [ md5, blowfish, sha256, sha512 ] salt: - description: Secret string that is used for the hashing, if none is provided a random one can be generated. - type: int + description: Secret string used for the hashing. If none is provided a random one can be generated. Use only numbers and letters (characters matching V([./0-9A-Za-z]+)). + type: string rounds: description: Number of encryption rounds, default varies by algorithm used. type: int diff --git a/lib/ansible/plugins/filter/regex_replace.yml b/lib/ansible/plugins/filter/regex_replace.yml index 8c8d0af..d139e9c 100644 --- a/lib/ansible/plugins/filter/regex_replace.yml +++ b/lib/ansible/plugins/filter/regex_replace.yml @@ -6,6 +6,8 @@ DOCUMENTATION: - Replace a substring defined by a regular expression with another defined by another regular expression based on the first match. notes: - Maps to Python's C(re.sub). + - 'The substring matched by the group is accessible via the symbolic group name or + the ``\{number}`` special sequence. See examples section.' positional: _input, _regex_match, _regex_replace options: _input: @@ -28,6 +30,16 @@ DOCUMENTATION: description: Force the search to be case insensitive if V(True), case sensitive otherwise. type: bool default: no + count: + description: Maximum number of pattern occurrences to replace. If zero, replace all occurrences. + type: int + default: 0 + version_added: "2.17" + mandatory_count: + description: Except a certain number of replacements. Raises an error otherwise. If zero, ignore. + type: int + default: 0 + version_added: "2.17" EXAMPLES: | @@ -46,6 +58,9 @@ EXAMPLES: | # piratecomment => '#CAR\n#tar\nfoo\n#bar\n' piratecomment: "{{ 'CAR\ntar\nfoo\nbar\n' | regex_replace('(?im)^(.ar)$', '#\\1') }}" + # 'foo=bar=baz' => 'foo:bar=baz' + key_value: "{{ 'foo=bar=baz' | regex_replace('=', ':', count=1) }}" + RETURN: _value: description: String with substitution (or original if no match). diff --git a/lib/ansible/plugins/filter/regex_search.yml b/lib/ansible/plugins/filter/regex_search.yml index 970de62..e9ac11d 100644 --- a/lib/ansible/plugins/filter/regex_search.yml +++ b/lib/ansible/plugins/filter/regex_search.yml @@ -6,6 +6,8 @@ DOCUMENTATION: - Search in a string to extract the part that matches the regular expression. notes: - Maps to Python's C(re.search). + - 'The substring matched by the group is accessible via the symbolic group name or + the ``\{number}`` special sequence. See examples section.' positional: _input, _regex options: _input: @@ -38,6 +40,16 @@ EXAMPLES: | # drinkat => 'BAR' drinkat: "{{ 'foo\nBAR' | regex_search('^bar', multiline=True, ignorecase=True) }}" + # Extracts server and database id from a string using number + # (the substring matched by the group is accessible via the \number special sequence) + db: "{{ 'server1/database42' | regex_search('server([0-9]+)/database([0-9]+)', '\\1', '\\2') }}" + # => ['1', '42'] + + # Extracts dividend and divisor from a division + # (the substring matched by the group is accessible via the symbolic group name) + db: "{{ '21/42' | regex_search('(?P<dividend>[0-9]+)/(?P<divisor>[0-9]+)', '\\g<dividend>', '\\g<divisor>') }}" + # => ['21', '42'] + RETURN: _value: description: Matched string or empty string if no match. diff --git a/lib/ansible/plugins/filter/strftime.yml b/lib/ansible/plugins/filter/strftime.yml index a1d8b92..9720729 100644 --- a/lib/ansible/plugins/filter/strftime.yml +++ b/lib/ansible/plugins/filter/strftime.yml @@ -21,6 +21,7 @@ DOCUMENTATION: description: Whether time supplied is in UTC. type: bool default: false + version_added: '2.14' EXAMPLES: | # for a complete set of features go to https://strftime.org/ @@ -39,15 +40,7 @@ EXAMPLES: | # Use arbitrary epoch value {{ '%Y-%m-%d' | strftime(0) }} # => 1970-01-01 - {{ '%Y-%m-%d' | strftime(1441357287) }} # => 2015-09-04 - - # complex examples - vars: - date1: '2022-11-15T03:23:13.686956868Z' - date2: '2021-12-15T16:06:24.400087Z' - date_short: '{{ date1|regex_replace("([^.]+)(\.\d{6})(\d*)(.+)", "\1\2\4") }}' #shorten microseconds - iso8601format: '%Y-%m-%dT%H:%M:%S.%fZ' - date_diff_isoed: '{{ (date1|to_datetime(isoformat) - date2|to_datetime(isoformat)).total_seconds() }}' + {{ '%Y-%m-%d' | strftime(seconds=1441357287, utc=true) }} # => 2015-09-04 RETURN: _value: diff --git a/lib/ansible/plugins/filter/to_datetime.yml b/lib/ansible/plugins/filter/to_datetime.yml index dbd476a..bc50732 100644 --- a/lib/ansible/plugins/filter/to_datetime.yml +++ b/lib/ansible/plugins/filter/to_datetime.yml @@ -4,9 +4,14 @@ DOCUMENTATION: short_description: Get C(datetime) from string description: - Using the input string attempt to create a matching Python C(datetime) object. + - Adding or Subtracting two datetime objects will result in a Python C(timedelta) object. notes: - For a full list of format codes for working with Python date format strings, see L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior). + - The timedelta object produced by the difference of two datetimes store the days, seconds, and microseconds of + the delta. This results in the C(seconds) attribute being the total seconds of the minutes and hours of that + delta. See L(datatime.timedelta, https://docs.python.org/3/library/datetime.html#timedelta-objects) for more + information about how a timedelta works. positional: _input options: _input: @@ -22,13 +27,23 @@ EXAMPLES: | # Get total amount of seconds between two dates. Default date format is %Y-%m-%d %H:%M:%S but you can pass your own format secsdiff: '{{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime("%Y-%m-%d"))).total_seconds() }}' - # Get remaining seconds after delta has been calculated. NOTE: This does NOT convert years, days, hours, and so on to seconds. For that, use total_seconds() + # Get remaining seconds after delta has been calculated. NOTE: This does NOT convert years and days to seconds. For that, use total_seconds() {{ (("2016-08-14 20:00:12" | to_datetime) - ("2016-08-14 18:00:00" | to_datetime)).seconds }} - # This expression evaluates to "12" and not "132". Delta is 2 hours, 12 seconds + # This expression evaluates to "7212". Delta is 2 hours, 12 seconds # get amount of days between two dates. This returns only number of days and discards remaining hours, minutes, and seconds {{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime('%Y-%m-%d'))).days }} + # difference between to dotnet (100ns precision) and iso8601 microsecond timestamps + # the date1_short regex replace will work for any timestamp that has a higher than microsecond precision + # by cutting off anything more precise than microseconds + vars: + date1: '2022-11-15T03:23:13.6869568Z' + date2: '2021-12-15T16:06:24.400087Z' + date1_short: '{{ date1|regex_replace("([^.]+)(\.\d{6})(\d*)(.+)", "\1\2\4") }}' # shorten to microseconds + iso8601format: '%Y-%m-%dT%H:%M:%S.%fZ' + date_diff_isoed: '{{ (date1_short|to_datetime(iso8601format) - date2|to_datetime(iso8601format)).total_seconds() }}' + RETURN: _value: description: C(datetime) object from the represented value. diff --git a/lib/ansible/plugins/filter/to_nice_json.yml b/lib/ansible/plugins/filter/to_nice_json.yml index f40e22c..fa31b26 100644 --- a/lib/ansible/plugins/filter/to_nice_json.yml +++ b/lib/ansible/plugins/filter/to_nice_json.yml @@ -39,6 +39,10 @@ DOCUMENTATION: description: If V(True), keys that are not basic Python types will be skipped. default: False type: bool + sort_keys: + description: Affects sorting of dictionary keys. + default: True + type: bool notes: - Both O(vault_to_text) and O(preprocess_unsafe) defaulted to V(False) between Ansible 2.9 and 2.12. - 'These parameters to C(json.dumps) will be ignored, they are overridden for internal use: I(cls), I(default), I(indent), I(separators), I(sort_keys).' diff --git a/lib/ansible/plugins/filter/union.yml b/lib/ansible/plugins/filter/union.yml index 7ef656d..d5e5c7a 100644 --- a/lib/ansible/plugins/filter/union.yml +++ b/lib/ansible/plugins/filter/union.yml @@ -29,7 +29,7 @@ EXAMPLES: | # list1: [1, 2, 5, 1, 3, 4, 10] # list2: [1, 2, 3, 4, 5, 11, 99] {{ list1 | union(list2) }} - # => [1, 2, 5, 1, 3, 4, 10, 11, 99] + # => [1, 2, 5, 3, 4, 10, 11, 99] RETURN: _value: description: A unique list of all the elements from both lists. diff --git a/lib/ansible/plugins/filter/urls.py b/lib/ansible/plugins/filter/urls.py index fb7abc6..1f9cde2 100644 --- a/lib/ansible/plugins/filter/urls.py +++ b/lib/ansible/plugins/filter/urls.py @@ -3,8 +3,7 @@ # Copyright: (c) 2012, Dag Wieers (@dagwieers) <dag@wieers.com> # 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 from functools import partial diff --git a/lib/ansible/plugins/filter/urlsplit.py b/lib/ansible/plugins/filter/urlsplit.py index 11c1f11..8963659 100644 --- a/lib/ansible/plugins/filter/urlsplit.py +++ b/lib/ansible/plugins/filter/urlsplit.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 DOCUMENTATION = r''' name: urlsplit diff --git a/lib/ansible/plugins/filter/zip.yml b/lib/ansible/plugins/filter/zip.yml index 96c307b..bc9e77d 100644 --- a/lib/ansible/plugins/filter/zip.yml +++ b/lib/ansible/plugins/filter/zip.yml @@ -5,7 +5,7 @@ DOCUMENTATION: positional: _input, _additional_lists description: Iterate over several iterables in parallel, producing tuples with an item from each one. notes: - - This is mostly a passhtrough to Python's C(zip) function. + - This is mostly a passthrough to Python's C(zip) function. options: _input: description: Original list. @@ -34,7 +34,7 @@ EXAMPLES: | shorter: "{{ [1,2,3] | zip(['a','b','c','d','e','f']) }}" # compose dict from lists of keys and values - mydcit: "{{ dict(keys_list | zip(values_list)) }}" + mydict: "{{ dict(keys_list | zip(values_list)) }}" RETURN: _value: diff --git a/lib/ansible/plugins/filter/zip_longest.yml b/lib/ansible/plugins/filter/zip_longest.yml index 964e9c2..36e6c2f 100644 --- a/lib/ansible/plugins/filter/zip_longest.yml +++ b/lib/ansible/plugins/filter/zip_longest.yml @@ -8,7 +8,7 @@ DOCUMENTATION: If the iterables are of uneven length, missing values are filled-in with O(fillvalue). Iteration continues until the longest iterable is exhausted. notes: - - This is mostly a passhtrough to Python's C(itertools.zip_longest) function + - This is mostly a passthrough to Python's C(itertools.zip_longest) function options: _input: description: Original list. diff --git a/lib/ansible/plugins/httpapi/__init__.py b/lib/ansible/plugins/httpapi/__init__.py index 0773921..e6c4f18 100644 --- a/lib/ansible/plugins/httpapi/__init__.py +++ b/lib/ansible/plugins/httpapi/__init__.py @@ -1,8 +1,7 @@ # (c) 2018 Red Hat Inc. # 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 from abc import abstractmethod diff --git a/lib/ansible/plugins/inventory/__init__.py b/lib/ansible/plugins/inventory/__init__.py index a68f596..f5bfed6 100644 --- a/lib/ansible/plugins/inventory/__init__.py +++ b/lib/ansible/plugins/inventory/__init__.py @@ -15,9 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <https://www.gnu.org/licenses/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import hashlib import os @@ -220,7 +218,7 @@ class BaseInventoryPlugin(AnsiblePlugin): try: # avoid loader cache so meta: refresh_inventory can pick up config changes # if we read more than once, fs cache should be good enough - config = self.loader.load_from_file(path, cache=False) + config = self.loader.load_from_file(path, cache='none') except Exception as e: raise AnsibleParserError(to_native(e)) diff --git a/lib/ansible/plugins/inventory/advanced_host_list.py b/lib/ansible/plugins/inventory/advanced_host_list.py index 3c5f52c..9ca45b6 100644 --- a/lib/ansible/plugins/inventory/advanced_host_list.py +++ b/lib/ansible/plugins/inventory/advanced_host_list.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = ''' name: advanced_host_list diff --git a/lib/ansible/plugins/inventory/auto.py b/lib/ansible/plugins/inventory/auto.py index 45941ca..9948385 100644 --- a/lib/ansible/plugins/inventory/auto.py +++ b/lib/ansible/plugins/inventory/auto.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = ''' name: auto @@ -37,7 +36,7 @@ class InventoryModule(BaseInventoryPlugin): return super(InventoryModule, self).verify_file(path) def parse(self, inventory, loader, path, cache=True): - config_data = loader.load_from_file(path, cache=False) + config_data = loader.load_from_file(path, cache='none') try: plugin_name = config_data.get('plugin', None) diff --git a/lib/ansible/plugins/inventory/constructed.py b/lib/ansible/plugins/inventory/constructed.py index 76b19e7..98f6178 100644 --- a/lib/ansible/plugins/inventory/constructed.py +++ b/lib/ansible/plugins/inventory/constructed.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = ''' name: constructed @@ -26,6 +25,8 @@ DOCUMENTATION = ''' - The host_group_vars (enabled by default) 'vars plugin' is the one responsible for reading host_vars/ and group_vars/ directories. - This will execute all vars plugins, even those that are not supposed to execute at the 'inventory' stage. See vars plugins docs for details on 'stage'. + - Implicit groups, such as 'all' or 'ungrouped', need to be explicitly defined in any previous inventory to apply the + corresponding group_vars required: false default: false type: boolean diff --git a/lib/ansible/plugins/inventory/generator.py b/lib/ansible/plugins/inventory/generator.py index 1955f36..ba697df 100644 --- a/lib/ansible/plugins/inventory/generator.py +++ b/lib/ansible/plugins/inventory/generator.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = ''' name: generator diff --git a/lib/ansible/plugins/inventory/host_list.py b/lib/ansible/plugins/inventory/host_list.py index d0b2dad..c9ffcc8 100644 --- a/lib/ansible/plugins/inventory/host_list.py +++ b/lib/ansible/plugins/inventory/host_list.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = r''' name: host_list diff --git a/lib/ansible/plugins/inventory/ini.py b/lib/ansible/plugins/inventory/ini.py index 1ff4bf1..e2efde1 100644 --- a/lib/ansible/plugins/inventory/ini.py +++ b/lib/ansible/plugins/inventory/ini.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 DOCUMENTATION = ''' name: ini diff --git a/lib/ansible/plugins/inventory/script.py b/lib/ansible/plugins/inventory/script.py index 48d9234..d3bfc8e 100644 --- a/lib/ansible/plugins/inventory/script.py +++ b/lib/ansible/plugins/inventory/script.py @@ -2,8 +2,7 @@ # 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 DOCUMENTATION = ''' name: script @@ -24,7 +23,7 @@ DOCUMENTATION = ''' - The source provided must be an executable that returns Ansible inventory JSON - The source must accept C(--list) and C(--host <hostname>) as arguments. C(--host) will only be used if no C(_meta) key is present. - This is a performance optimization as the script would be called per host otherwise. + This is a performance optimization as the script would be called one additional time per host otherwise. notes: - Enabled in configuration by default. - The plugin does not cache results because external inventory scripts are responsible for their own caching. @@ -32,6 +31,126 @@ DOCUMENTATION = ''' - To find the scripts that used to be part of the code release, go to U(https://github.com/ansible-community/contrib-scripts/). ''' +EXAMPLES = r'''# fmt: code + +### simple bash script + + #!/usr/bin/env bash + + if [ "$1" == "--list" ]; then + cat<<EOF + { + "bash_hosts": { + "hosts": [ + "myhost.domain.com", + "myhost2.domain.com" + ], + "vars": { + "host_test": "test-value" + } + }, + "_meta": { + "hostvars": { + "myhost.domain.com": { + "host_specific_test_var": "test-value" + } + } + } + } + EOF + elif [ "$1" == "--host" ]; then + # this should not normally be called by Ansible as we return _meta above + if [ "$2" == "myhost.domain.com" ]; then + echo '{"_meta": {hostvars": {"myhost.domain.com": {"host_specific-test_var": "test-value"}}}}' + else + echo '{"_meta": {hostvars": {}}}' + fi + else + echo "Invalid option: use --list or --host <hostname>" + exit 1 + fi + + +### python example with ini config + + #!/usr/bin/env python + """ + # ansible_inventory.py + """ + import argparse + import json + import os.path + import sys + from configparser import ConfigParser + from inventories.custom import MyInventoryAPI + + def load_config() -> ConfigParser: + cp = ConfigParser() + config_file = os.path.expanduser("~/.config/ansible_inventory_script.cfg") + cp.read(config_file) + if not cp.has_option('DEFAULT', 'namespace'): + raise ValueError("Missing configuration option: DEFAULT -> namespace") + return cp + + + def get_api_data(namespace: str, pretty=False) -> str: + """ + :param namespace: parameter for our custom api + :param pretty: Human redable JSON vs machine readable + :return: JSON string + """ + found_data = list(MyInventoryAPI(namespace)) + hostvars = {} + data = { '_meta': { 'hostvars': {}},} + + groups = found_data['groups'].keys() + for group in groups: + groups[group]['hosts'] = found_data[groups].get('host_list', []) + if group not in data: + data[group] = {} + data[group]['hosts'] = found_data[groups].get('host_list', []) + data[group]['vars'] = found_data[groups].get('info', []) + data[group]['children'] = found_data[group].get('subgroups', []) + + for host_data in found_data['hosts']: + for name in host_data.items(): + # turn info into vars + data['_meta'][name] = found_data[name].get('info', {}) + # set ansible_host if possible + if 'address' in found_data[name]: + data[name]['_meta']['ansible_host'] = found_data[name]['address'] + data['_meta']['hostvars'] = hostvars + + return json.dumps(data, indent=pretty) + + if __name__ == '__main__': + + arg_parser = argparse.ArgumentParser( description=__doc__, prog=__file__) + arg_parser.add_argument('--pretty', action='store_true', default=False, help="Pretty JSON") + mandatory_options = arg_parser.add_mutually_exclusive_group() + mandatory_options.add_argument('--list', action='store', nargs="*", help="Get inventory JSON from our API") + mandatory_options.add_argument('--host', action='store', + help="Get variables for specific host, not used but kept for compatability") + + try: + config = load_config() + namespace = config.get('DEFAULT', 'namespace') + + args = arg_parser.parse_args() + if args.host: + print('{"_meta":{}}') + sys.stderr.write('This script already provides _meta via --list, so this option is really ignored') + elif len(args.list) >= 0: + print(get_api_data(namespace, args.pretty)) + else: + raise ValueError("Valid options are --list or --host <HOSTNAME>") + + except ValueError: + raise + +''' + + import os import subprocess diff --git a/lib/ansible/plugins/inventory/toml.py b/lib/ansible/plugins/inventory/toml.py index 1c2b439..39a3d5c 100644 --- a/lib/ansible/plugins/inventory/toml.py +++ b/lib/ansible/plugins/inventory/toml.py @@ -1,8 +1,7 @@ # Copyright (c) 2018 Matt Martz <matt@sivel.net> # 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 DOCUMENTATION = r''' name: toml diff --git a/lib/ansible/plugins/inventory/yaml.py b/lib/ansible/plugins/inventory/yaml.py index 79af3dc..3625ed4 100644 --- a/lib/ansible/plugins/inventory/yaml.py +++ b/lib/ansible/plugins/inventory/yaml.py @@ -1,8 +1,7 @@ # 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 DOCUMENTATION = ''' name: yaml @@ -102,7 +101,7 @@ class InventoryModule(BaseFileInventoryPlugin): self.set_options() try: - data = self.loader.load_from_file(path, cache=False) + data = self.loader.load_from_file(path, cache='none') except Exception as e: raise AnsibleParserError(e) @@ -114,7 +113,7 @@ class InventoryModule(BaseFileInventoryPlugin): raise AnsibleParserError('Plugin configuration YAML file, not YAML inventory') # We expect top level keys to correspond to groups, iterate over them - # to get host, vars and subgroups (which we iterate over recursivelly) + # to get host, vars and subgroups (which we iterate over recursively) if isinstance(data, MutableMapping): for group_name in data: self._parse_group(group_name, data[group_name]) diff --git a/lib/ansible/plugins/list.py b/lib/ansible/plugins/list.py index cd4d51f..18cbd45 100644 --- a/lib/ansible/plugins/list.py +++ b/lib/ansible/plugins/list.py @@ -1,8 +1,7 @@ # (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 @@ -35,7 +34,7 @@ def get_composite_name(collection, name, path, depth): resolved_collection = 'ansible.builtin' resource_name = '.'.join(name.split(f"{resolved_collection}.")[1:]) - # collectionize name + # create FQCN composite = [resolved_collection] if depth: composite.extend(path.split(os.path.sep)[depth * -1:]) diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py index 9ff19bb..c865ac4 100644 --- a/lib/ansible/plugins/loader.py +++ b/lib/ansible/plugins/loader.py @@ -4,8 +4,7 @@ # (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 glob import os @@ -15,6 +14,7 @@ import sys import warnings from collections import defaultdict, namedtuple +from importlib import import_module from traceback import format_exc import ansible.module_utils.compat.typing as t @@ -26,7 +26,6 @@ from ansible import __version__ as ansible_version from ansible import constants as C from ansible.errors import AnsibleError, AnsiblePluginCircularRedirect, AnsiblePluginRemovedError, AnsibleCollectionUnsupportedVersionError from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native -from ansible.module_utils.compat.importlib import import_module from ansible.module_utils.six import string_types from ansible.parsing.utils.yaml import from_yaml from ansible.parsing.yaml.loader import AnsibleLoader @@ -1232,24 +1231,22 @@ class Jinja2Loader(PluginLoader): # check deprecations deprecation_entry = routing_entry.get('deprecation') if deprecation_entry: - warning_text = deprecation_entry.get('warning_text') + warning_text = deprecation_entry.get('warning_text') or '' removal_date = deprecation_entry.get('removal_date') removal_version = deprecation_entry.get('removal_version') - if not warning_text: - warning_text = '{0} "{1}" is deprecated'.format(self.type, key) + warning_text = f'{self.type.title()} "{key}" has been deprecated.{" " if warning_text else ""}{warning_text}' display.deprecated(warning_text, version=removal_version, date=removal_date, collection_name=acr.collection) # check removal tombstone_entry = routing_entry.get('tombstone') if tombstone_entry: - warning_text = tombstone_entry.get('warning_text') + warning_text = tombstone_entry.get('warning_text') or '' removal_date = tombstone_entry.get('removal_date') removal_version = tombstone_entry.get('removal_version') - if not warning_text: - warning_text = '{0} "{1}" has been removed'.format(self.type, key) + warning_text = f'{self.type.title()} "{key}" has been removed.{" " if warning_text else ""}{warning_text}' exc_msg = display.get_deprecation_message(warning_text, version=removal_version, date=removal_date, collection_name=acr.collection, removed=True) @@ -1299,12 +1296,14 @@ class Jinja2Loader(PluginLoader): fq_name = '.'.join((parent_prefix, func_name)) src_name = f"ansible_collections.{acr.collection}.plugins.{self.type}.{acr.subdirs}.{func_name}" # TODO: load anyways into CACHE so we only match each at end of loop - # the files themseves should already be cached by base class caching of modules(python) + # the files themselves should already be cached by base class caching of modules(python) if key in (func_name, fq_name): plugin = self._plugin_wrapper_type(func) if plugin: context = plugin_impl.plugin_load_context self._update_object(plugin, src_name, plugin_impl.object._original_path, resolved=fq_name) + # context will have filename, which for tests/filters might not be correct + context._resolved_fqcn = plugin.ansible_name # FIXME: once we start caching these results, we'll be missing functions that would have loaded later break # go to next file as it can override if dupe (dont break both loops) @@ -1448,7 +1447,7 @@ def _load_plugin_filter(): display.warning(u'The plugin filter file, {0} does not exist.' u' Skipping.'.format(filter_cfg)) - # Specialcase the stat module as Ansible can run very few things if stat is rejected + # Special case: the stat module as Ansible can run very few things if stat is rejected if 'stat' in filters['ansible.modules']: raise AnsibleError('The stat module was specified in the module reject list file, {0}, but' ' Ansible will not function without the stat module. Please remove stat' diff --git a/lib/ansible/plugins/lookup/__init__.py b/lib/ansible/plugins/lookup/__init__.py index c9779d6..bc15943 100644 --- a/lib/ansible/plugins/lookup/__init__.py +++ b/lib/ansible/plugins/lookup/__init__.py @@ -15,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations from abc import abstractmethod diff --git a/lib/ansible/plugins/lookup/config.py b/lib/ansible/plugins/lookup/config.py index b476b53..4c6b000 100644 --- a/lib/ansible/plugins/lookup/config.py +++ b/lib/ansible/plugins/lookup/config.py @@ -1,42 +1,46 @@ # (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 DOCUMENTATION = """ name: config author: Ansible Core Team version_added: "2.5" - short_description: Lookup current Ansible configuration values + short_description: Display the 'resolved' Ansible option values. description: - - Retrieves the value of an Ansible configuration setting. - - You can use C(ansible-config list) to see all available settings. + - Retrieves the value of an Ansible configuration setting, resolving all sources, from defaults, ansible.cfg, envirionmnet, + CLI, and variables, but not keywords. + - The values returned assume the context of the current host or C(inventory_hostname). + - You can use C(ansible-config list) to see the global available settings, add C(-t all) to also show plugin options. options: _terms: - description: The key(s) to look up + description: The option(s) to look up. required: True on_missing: - description: - - action to take if term is missing from config - - Error will raise a fatal error - - Skip will just ignore the term - - Warn will skip over it but issue a warning + description: Action to take if term is missing from config default: error type: string - choices: ['error', 'skip', 'warn'] + choices: + error: Issue an error message and raise fatal signal + warn: Issue a warning message and continue + skip: Silently ignore plugin_type: - description: the type of the plugin referenced by 'plugin_name' option. + description: The type of the plugin referenced by 'plugin_name' option. choices: ['become', 'cache', 'callback', 'cliconf', 'connection', 'httpapi', 'inventory', 'lookup', 'netconf', 'shell', 'vars'] type: string version_added: '2.12' plugin_name: - description: name of the plugin for which you want to retrieve configuration settings. + description: The name of the plugin for which you want to retrieve configuration settings. type: string version_added: '2.12' show_origin: - description: toggle the display of what configuration subsystem the value came from + description: Set this to return what configuration subsystem the value came from + (defaults, config file, environment, CLI, or variables). type: bool version_added: '2.16' + notes: + - Be aware that currently this lookup cannot take keywords nor delegation into account, + so for options that support keywords or are affected by delegation, it is at best a good guess or approximation. """ EXAMPLES = """ diff --git a/lib/ansible/plugins/lookup/csvfile.py b/lib/ansible/plugins/lookup/csvfile.py index 76d97ed..9d199d8 100644 --- a/lib/ansible/plugins/lookup/csvfile.py +++ b/lib/ansible/plugins/lookup/csvfile.py @@ -1,8 +1,7 @@ # (c) 2013, Jan-Piet Mens <jpmens(at)gmail.com> # (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 DOCUMENTATION = r""" name: csvfile @@ -17,6 +16,11 @@ DOCUMENTATION = r""" col: description: column to return (0 indexed). default: "1" + keycol: + description: column to search in (0 indexed). + default: 0 + type: int + version_added: "2.17" default: description: what to return if the value is not found in the file. delimiter: @@ -83,7 +87,7 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_ class CSVRecoder: """ - Iterator that reads an encoded stream and reencodes the input to UTF-8 + Iterator that reads an encoded stream and encodes the input to UTF-8 """ def __init__(self, f, encoding='utf-8'): self.reader = codecs.getreader(encoding)(f) @@ -123,14 +127,14 @@ class CSVReader: class LookupModule(LookupBase): - def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1): + def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1, keycol=0): try: f = open(to_bytes(filename), 'rb') creader = CSVReader(f, delimiter=to_native(delimiter), encoding=encoding) for row in creader: - if len(row) and row[0] == key: + if len(row) and row[keycol] == key: return row[int(col)] except Exception as e: raise AnsibleError("csvfile: %s" % to_native(e)) @@ -156,6 +160,7 @@ class LookupModule(LookupBase): # parameters override per term using k/v try: + reset_params = False for name, value in kv.items(): if name == '_raw_params': continue @@ -163,7 +168,11 @@ class LookupModule(LookupBase): raise AnsibleAssertionError('%s is not a valid option' % name) self._deprecate_inline_kv() - paramvals[name] = value + self.set_option(name, value) + reset_params = True + + if reset_params: + paramvals = self.get_options() except (ValueError, AssertionError) as e: raise AnsibleError(e) @@ -173,7 +182,7 @@ class LookupModule(LookupBase): paramvals['delimiter'] = "\t" lookupfile = self.find_file_in_search_path(variables, 'files', paramvals['file']) - var = self.read_csv(lookupfile, key, paramvals['delimiter'], paramvals['encoding'], paramvals['default'], paramvals['col']) + var = self.read_csv(lookupfile, key, paramvals['delimiter'], paramvals['encoding'], paramvals['default'], paramvals['col'], paramvals['keycol']) if var is not None: if isinstance(var, MutableSequence): for v in var: diff --git a/lib/ansible/plugins/lookup/dict.py b/lib/ansible/plugins/lookup/dict.py index af9a081..a8c1089 100644 --- a/lib/ansible/plugins/lookup/dict.py +++ b/lib/ansible/plugins/lookup/dict.py @@ -1,8 +1,7 @@ # (c) 2014, Kent R. Spillner <kspillner@acm.org> # (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 DOCUMENTATION = """ name: dict @@ -49,7 +48,7 @@ tasks: RETURN = """ _list: description: - - list of composed dictonaries with key and value + - list of composed dictionaries with key and value type: list """ diff --git a/lib/ansible/plugins/lookup/env.py b/lib/ansible/plugins/lookup/env.py index db34d8d..50547a8 100644 --- a/lib/ansible/plugins/lookup/env.py +++ b/lib/ansible/plugins/lookup/env.py @@ -1,8 +1,7 @@ # (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com> # (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 DOCUMENTATION = """ name: env @@ -56,11 +55,12 @@ RETURN = """ type: list """ +import os + from jinja2.runtime import Undefined from ansible.errors import AnsibleUndefinedVariable from ansible.plugins.lookup import LookupBase -from ansible.utils import py3compat class LookupModule(LookupBase): @@ -72,7 +72,7 @@ class LookupModule(LookupBase): d = self.get_option('default') for term in terms: var = term.split()[0] - val = py3compat.environ.get(var, d) + val = os.environ.get(var, d) if isinstance(val, Undefined): raise AnsibleUndefinedVariable('The "env" lookup, found an undefined variable: %s' % var) ret.append(val) diff --git a/lib/ansible/plugins/lookup/file.py b/lib/ansible/plugins/lookup/file.py index 25946b2..17338c0 100644 --- a/lib/ansible/plugins/lookup/file.py +++ b/lib/ansible/plugins/lookup/file.py @@ -1,8 +1,7 @@ # (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> # (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 DOCUMENTATION = """ name: file diff --git a/lib/ansible/plugins/lookup/fileglob.py b/lib/ansible/plugins/lookup/fileglob.py index 00d5f09..5ab730d 100644 --- a/lib/ansible/plugins/lookup/fileglob.py +++ b/lib/ansible/plugins/lookup/fileglob.py @@ -1,8 +1,7 @@ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (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 DOCUMENTATION = """ name: fileglob diff --git a/lib/ansible/plugins/lookup/first_found.py b/lib/ansible/plugins/lookup/first_found.py index 6862880..a68791b 100644 --- a/lib/ansible/plugins/lookup/first_found.py +++ b/lib/ansible/plugins/lookup/first_found.py @@ -1,8 +1,7 @@ # (c) 2013, seth vidal <skvidal@fedoraproject.org> red hat, inc # (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 DOCUMENTATION = """ name: first_found @@ -147,6 +146,7 @@ from jinja2.exceptions import UndefinedError from ansible.errors import AnsibleLookupError, AnsibleUndefinedVariable from ansible.module_utils.six import string_types from ansible.plugins.lookup import LookupBase +from ansible.utils.path import unfrackpath def _splitter(value, chars): @@ -198,7 +198,7 @@ class LookupModule(LookupBase): # NOTE: this is used as 'global' but can be set many times?!?!? skip = self.get_option('skip') - # magic extra spliting to create lists + # magic extra splitting to create lists filelist = _split_on(files, ',;') pathlist = _split_on(paths, ',:;') @@ -209,7 +209,7 @@ class LookupModule(LookupBase): f = os.path.join(path, fn) total_search.append(f) elif filelist: - # NOTE: this is now 'extend', previouslly it would clobber all options, but we deemed that a bug + # NOTE: this is now 'extend', previously it would clobber all options, but we deemed that a bug total_search.extend(filelist) else: total_search.append(term) @@ -218,8 +218,9 @@ class LookupModule(LookupBase): def run(self, terms, variables, **kwargs): + self.set_options(var_options=variables, direct=kwargs) + if not terms: - self.set_options(var_options=variables, direct=kwargs) terms = self.get_option('files') total_search, skip = self._process_terms(terms, variables, kwargs) @@ -246,10 +247,10 @@ class LookupModule(LookupBase): # exit if we find one! if path is not None: - return [path] + return [unfrackpath(path, follow=False)] # if we get here, no file was found if skip: - # NOTE: global skip wont matter, only last 'skip' value in dict term + # NOTE: global skip won't matter, only last 'skip' value in dict term return [] raise AnsibleLookupError("No file was found when using first_found.") diff --git a/lib/ansible/plugins/lookup/indexed_items.py b/lib/ansible/plugins/lookup/indexed_items.py index f63a895..fe919cd 100644 --- a/lib/ansible/plugins/lookup/indexed_items.py +++ b/lib/ansible/plugins/lookup/indexed_items.py @@ -1,8 +1,7 @@ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (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 DOCUMENTATION = """ name: indexed_items diff --git a/lib/ansible/plugins/lookup/ini.py b/lib/ansible/plugins/lookup/ini.py index 9467676..cdc9a15 100644 --- a/lib/ansible/plugins/lookup/ini.py +++ b/lib/ansible/plugins/lookup/ini.py @@ -1,8 +1,7 @@ # (c) 2015, Yannig Perre <yannig.perre(at)gmail.com> # (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 DOCUMENTATION = """ name: ini @@ -155,16 +154,20 @@ class LookupModule(LookupBase): params = _parse_params(term, paramvals) try: updated_key = False + updated_options = False for param in params: if '=' in param: name, value = param.split('=') if name not in paramvals: raise AnsibleLookupError('%s is not a valid option.' % name) - paramvals[name] = value + self.set_option(name, value) + updated_options = True elif key == term: # only take first, this format never supported multiple keys inline key = param updated_key = True + if updated_options: + paramvals = self.get_options() except ValueError as e: # bad params passed raise AnsibleLookupError("Could not use '%s' from '%s': %s" % (param, params, to_native(e)), orig_exc=e) diff --git a/lib/ansible/plugins/lookup/inventory_hostnames.py b/lib/ansible/plugins/lookup/inventory_hostnames.py index 4fa1d68..e9ba61b 100644 --- a/lib/ansible/plugins/lookup/inventory_hostnames.py +++ b/lib/ansible/plugins/lookup/inventory_hostnames.py @@ -3,8 +3,7 @@ # (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 DOCUMENTATION = """ name: inventory_hostnames diff --git a/lib/ansible/plugins/lookup/items.py b/lib/ansible/plugins/lookup/items.py index 162c1e7..058ba97 100644 --- a/lib/ansible/plugins/lookup/items.py +++ b/lib/ansible/plugins/lookup/items.py @@ -1,8 +1,7 @@ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (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 DOCUMENTATION = """ name: items diff --git a/lib/ansible/plugins/lookup/lines.py b/lib/ansible/plugins/lookup/lines.py index 6314e37..7b08acf 100644 --- a/lib/ansible/plugins/lookup/lines.py +++ b/lib/ansible/plugins/lookup/lines.py @@ -2,8 +2,7 @@ # (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 DOCUMENTATION = """ name: lines diff --git a/lib/ansible/plugins/lookup/list.py b/lib/ansible/plugins/lookup/list.py index 6c553ae..a953f68 100644 --- a/lib/ansible/plugins/lookup/list.py +++ b/lib/ansible/plugins/lookup/list.py @@ -1,10 +1,8 @@ # (c) 2012-17 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) +from __future__ import annotations -__metaclass__ = type DOCUMENTATION = """ name: list diff --git a/lib/ansible/plugins/lookup/nested.py b/lib/ansible/plugins/lookup/nested.py index e768dba..097c2a4 100644 --- a/lib/ansible/plugins/lookup/nested.py +++ b/lib/ansible/plugins/lookup/nested.py @@ -1,8 +1,7 @@ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (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 DOCUMENTATION = """ name: nested diff --git a/lib/ansible/plugins/lookup/password.py b/lib/ansible/plugins/lookup/password.py index 1fe97f1..84894e2 100644 --- a/lib/ansible/plugins/lookup/password.py +++ b/lib/ansible/plugins/lookup/password.py @@ -3,8 +3,7 @@ # (c) 2013, Maykel Moya <mmoya@speedyrails.com> # (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 DOCUMENTATION = """ name: password @@ -332,29 +331,32 @@ class LookupModule(LookupBase): if invalid_params: raise AnsibleError('Unrecognized parameter(s) given to password lookup: %s' % ', '.join(invalid_params)) - # Set defaults - params['length'] = int(params.get('length', self.get_option('length'))) - params['encrypt'] = params.get('encrypt', self.get_option('encrypt')) - params['ident'] = params.get('ident', self.get_option('ident')) - params['seed'] = params.get('seed', self.get_option('seed')) + # update options with what we got + if params: + self.set_options(direct=params) - params['chars'] = params.get('chars', self.get_option('chars')) - if params['chars'] and isinstance(params['chars'], string_types): + # chars still might need more + chars = params.get('chars', self.get_option('chars')) + if chars and isinstance(chars, string_types): tmp_chars = [] - if u',,' in params['chars']: + if u',,' in chars: tmp_chars.append(u',') - tmp_chars.extend(c for c in params['chars'].replace(u',,', u',').split(u',') if c) - params['chars'] = tmp_chars + tmp_chars.extend(c for c in chars.replace(u',,', u',').split(u',') if c) + self.set_option('chars', tmp_chars) + + # return processed params + for field in VALID_PARAMS: + params[field] = self.get_option(field) return relpath, params def run(self, terms, variables, **kwargs): ret = [] - self.set_options(var_options=variables, direct=kwargs) - for term in terms: + self.set_options(var_options=variables, direct=kwargs) + changed = None relpath, params = self._parse_parameters(term) path = self._loader.path_dwim(relpath) diff --git a/lib/ansible/plugins/lookup/pipe.py b/lib/ansible/plugins/lookup/pipe.py index 20e922b..0923f13 100644 --- a/lib/ansible/plugins/lookup/pipe.py +++ b/lib/ansible/plugins/lookup/pipe.py @@ -1,8 +1,7 @@ # (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> # (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 DOCUMENTATION = r""" name: pipe diff --git a/lib/ansible/plugins/lookup/random_choice.py b/lib/ansible/plugins/lookup/random_choice.py index 93e6c2e..2e43d2e 100644 --- a/lib/ansible/plugins/lookup/random_choice.py +++ b/lib/ansible/plugins/lookup/random_choice.py @@ -1,8 +1,7 @@ # (c) 2013, Michael DeHaan <michael.dehaan@gmail.com> # (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 DOCUMENTATION = """ name: random_choice diff --git a/lib/ansible/plugins/lookup/sequence.py b/lib/ansible/plugins/lookup/sequence.py index f4fda43..9efe7ce 100644 --- a/lib/ansible/plugins/lookup/sequence.py +++ b/lib/ansible/plugins/lookup/sequence.py @@ -1,8 +1,7 @@ # (c) 2013, Jayson Vantuyl <jayson@aggressive.ly> # (c) 2012-17 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 DOCUMENTATION = """ name: sequence @@ -20,21 +19,21 @@ DOCUMENTATION = """ options: start: description: number at which to start the sequence - default: 0 + default: 1 type: integer end: description: number at which to end the sequence, dont use this with count type: integer - default: 0 count: description: number of elements in the sequence, this is not to be used with end type: integer - default: 0 stride: description: increments between sequence numbers, the default is 1 unless the end is less than the start, then it is -1. type: integer + default: 1 format: description: return a string with the generated number formatted in + default: "%d" """ EXAMPLES = """ @@ -98,6 +97,7 @@ SHORTCUT = re_compile( "(:(.+))?$", # Group 5, Group 6: Format String IGNORECASE ) +FIELDS = frozenset(('start', 'end', 'stride', 'count', 'format')) class LookupModule(LookupBase): @@ -139,30 +139,12 @@ class LookupModule(LookupBase): calculating the number of entries in a sequence when a stride is specified. """ - def reset(self): - """set sensible defaults""" - self.start = 1 - self.count = None - self.end = None - self.stride = 1 - self.format = "%d" - def parse_kv_args(self, args): """parse key-value style arguments""" - for arg in ["start", "end", "count", "stride"]: - try: - arg_raw = args.pop(arg, None) - if arg_raw is None: - continue - arg_cooked = int(arg_raw, 0) - setattr(self, arg, arg_cooked) - except ValueError: - raise AnsibleError( - "can't parse %s=%s as integer" - % (arg, arg_raw) - ) - if 'format' in args: - self.format = args.pop("format") + for arg in FIELDS: + value = args.pop(arg, None) + if value is not None: + self.set_option(arg, value) if args: raise AnsibleError( "unrecognized arguments to with_sequence: %s" @@ -177,33 +159,17 @@ class LookupModule(LookupBase): dummy, start, end, dummy, stride, dummy, format = match.groups() - if start is not None: - try: - start = int(start, 0) - except ValueError: - raise AnsibleError("can't parse start=%s as integer" % start) - if end is not None: - try: - end = int(end, 0) - except ValueError: - raise AnsibleError("can't parse end=%s as integer" % end) - if stride is not None: - try: - stride = int(stride, 0) - except ValueError: - raise AnsibleError("can't parse stride=%s as integer" % stride) - - if start is not None: - self.start = start - if end is not None: - self.end = end - if stride is not None: - self.stride = stride - if format is not None: - self.format = format + for key in FIELDS: + value = locals().get(key, None) + if value is not None: + self.set_option(key, value) return True + def set_fields(self): + for f in FIELDS: + setattr(self, f, self.get_option(f)) + def sanity_check(self): if self.count is None and self.end is None: raise AnsibleError("must specify count or end in with_sequence") @@ -246,7 +212,8 @@ class LookupModule(LookupBase): for term in terms: try: - self.reset() # clear out things for this iteration + # set defaults/global + self.set_options(direct=kwargs) try: if not self.parse_simple_args(term): self.parse_kv_args(parse_kv(term)) @@ -255,7 +222,9 @@ class LookupModule(LookupBase): except Exception as e: raise AnsibleError("unknown error parsing with_sequence arguments: %r. Error was: %s" % (term, e)) + self.set_fields() self.sanity_check() + if self.stride != 0: results.extend(self.generate_sequence()) except AnsibleError: diff --git a/lib/ansible/plugins/lookup/subelements.py b/lib/ansible/plugins/lookup/subelements.py index f221652..e269be5 100644 --- a/lib/ansible/plugins/lookup/subelements.py +++ b/lib/ansible/plugins/lookup/subelements.py @@ -1,8 +1,7 @@ # (c) 2013, Serge van Ginderachter <serge@vanginderachter.be> # (c) 2012-17 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 DOCUMENTATION = """ name: subelements diff --git a/lib/ansible/plugins/lookup/template.py b/lib/ansible/plugins/lookup/template.py index 358fa1d..b2508d0 100644 --- a/lib/ansible/plugins/lookup/template.py +++ b/lib/ansible/plugins/lookup/template.py @@ -2,8 +2,7 @@ # Copyright: (c) 2012-17, 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 DOCUMENTATION = """ name: template diff --git a/lib/ansible/plugins/lookup/together.py b/lib/ansible/plugins/lookup/together.py index c990e06..0d0bfd9 100644 --- a/lib/ansible/plugins/lookup/together.py +++ b/lib/ansible/plugins/lookup/together.py @@ -1,8 +1,7 @@ # (c) 2013, Bradley Young <young.bradley@gmail.com> # (c) 2012-17 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 DOCUMENTATION = """ name: together diff --git a/lib/ansible/plugins/lookup/unvault.py b/lib/ansible/plugins/lookup/unvault.py index d7f3cba..f2db18e 100644 --- a/lib/ansible/plugins/lookup/unvault.py +++ b/lib/ansible/plugins/lookup/unvault.py @@ -1,7 +1,6 @@ # (c) 2020 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 DOCUMENTATION = """ name: unvault diff --git a/lib/ansible/plugins/lookup/url.py b/lib/ansible/plugins/lookup/url.py index f5c93f2..05ebe6d 100644 --- a/lib/ansible/plugins/lookup/url.py +++ b/lib/ansible/plugins/lookup/url.py @@ -1,8 +1,7 @@ # (c) 2015, Brian Coca <bcoca@ansible.com> # (c) 2012-17 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 DOCUMENTATION = """ name: url @@ -88,7 +87,7 @@ options: - section: url_lookup key: force_basic_auth follow_redirects: - description: String of urllib2, all/yes, safe, none to determine how redirects are followed, see RedirectHandlerFactory for more information + description: String of urllib2, all/yes, safe, none to determine how redirects are followed type: string version_added: "2.10" default: 'urllib2' @@ -99,6 +98,13 @@ options: ini: - section: url_lookup key: follow_redirects + choices: + all: Will follow all redirects. + none: Will not follow any redirects. + safe: Only redirects doing GET or HEAD requests will be followed. + urllib2: Defer to urllib2 behavior (As of writing this follows HTTP redirects). + 'no': (DEPRECATED, will be removed in the future version) alias of V(none). + 'yes': (DEPRECATED, will be removed in the future version) alias of V(all). use_gssapi: description: - Use GSSAPI handler of requests diff --git a/lib/ansible/plugins/lookup/varnames.py b/lib/ansible/plugins/lookup/varnames.py index 4fd0153..2163ce7 100644 --- a/lib/ansible/plugins/lookup/varnames.py +++ b/lib/ansible/plugins/lookup/varnames.py @@ -1,7 +1,6 @@ # (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 DOCUMENTATION = """ name: varnames diff --git a/lib/ansible/plugins/lookup/vars.py b/lib/ansible/plugins/lookup/vars.py index dd5f763..14cac99 100644 --- a/lib/ansible/plugins/lookup/vars.py +++ b/lib/ansible/plugins/lookup/vars.py @@ -1,7 +1,6 @@ # (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 DOCUMENTATION = """ name: vars diff --git a/lib/ansible/plugins/netconf/__init__.py b/lib/ansible/plugins/netconf/__init__.py index 1344d63..6887d78 100644 --- a/lib/ansible/plugins/netconf/__init__.py +++ b/lib/ansible/plugins/netconf/__init__.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 from abc import abstractmethod from functools import wraps diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py index c9f8add..5aa0a1b 100644 --- a/lib/ansible/plugins/shell/__init__.py +++ b/lib/ansible/plugins/shell/__init__.py @@ -14,8 +14,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 os.path @@ -65,12 +64,12 @@ class ShellBase(AnsiblePlugin): # TODO: config system should already resolve this so we should be able to just iterate over dicts env = self.get_option('environment') if isinstance(env, string_types): - raise AnsibleError('The "envirionment" keyword takes a list of dictionaries or a dictionary, not a string') + raise AnsibleError('The "environment" keyword takes a list of dictionaries or a dictionary, not a string') if not isinstance(env, Sequence): env = [env] for env_dict in env: if not isinstance(env_dict, Mapping): - raise AnsibleError('The "envirionment" keyword takes a list of dictionaries (or single dictionary), but got a "%s" instead' % type(env_dict)) + raise AnsibleError('The "environment" keyword takes a list of dictionaries (or single dictionary), but got a "%s" instead' % type(env_dict)) self.env.update(env_dict) # We can remove the try: except in the future when we make ShellBase a proper subset of diff --git a/lib/ansible/plugins/shell/cmd.py b/lib/ansible/plugins/shell/cmd.py index 152fdd0..db851df 100644 --- a/lib/ansible/plugins/shell/cmd.py +++ b/lib/ansible/plugins/shell/cmd.py @@ -1,7 +1,6 @@ # Copyright (c) 2019 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 DOCUMENTATION = ''' name: cmd diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py index f2e78cb..405211a 100644 --- a/lib/ansible/plugins/shell/powershell.py +++ b/lib/ansible/plugins/shell/powershell.py @@ -1,8 +1,7 @@ # Copyright (c) 2014, Chris Church <chris@ninemoreminutes.com> # 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 DOCUMENTATION = ''' name: powershell diff --git a/lib/ansible/plugins/shell/sh.py b/lib/ansible/plugins/shell/sh.py index 146c466..e0412b7 100644 --- a/lib/ansible/plugins/shell/sh.py +++ b/lib/ansible/plugins/shell/sh.py @@ -1,8 +1,7 @@ # Copyright (c) 2014, Chris Church <chris@ninemoreminutes.com> # 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 DOCUMENTATION = ''' name: sh diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index eb2f76d..efd69ef 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -15,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import cmd import functools @@ -555,12 +553,19 @@ class StrategyBase: seen = [] for handler in handlers: if listeners := handler.listen: - if notification in handler.get_validated_value( + listeners = handler.get_validated_value( 'listen', handler.fattributes.get('listen'), listeners, templar, - ): + ) + if handler._role is not None: + for listener in listeners.copy(): + listeners.extend([ + handler._role.get_name(include_role_fqcn=True) + ' : ' + listener, + handler._role.get_name(include_role_fqcn=False) + ' : ' + listener + ]) + if notification in listeners: if handler.name and handler.name in seen: continue seen.append(handler.name) @@ -845,7 +850,7 @@ class StrategyBase: return ti_copy - def _load_included_file(self, included_file, iterator, is_handler=False): + def _load_included_file(self, included_file, iterator, is_handler=False, handle_stats_and_callbacks=True): ''' Loads an included YAML file of tasks, applying the optional set of variables. @@ -853,6 +858,15 @@ class StrategyBase: in such case the caller is responsible for marking the host(s) as failed using PlayIterator.mark_host_failed(). ''' + if handle_stats_and_callbacks: + display.deprecated( + "Reporting play recap stats and running callbacks functionality for " + "``include_tasks`` in ``StrategyBase._load_included_file`` is deprecated. " + "See ``https://github.com/ansible/ansible/pull/79260`` for guidance on how to " + "move the reporting into specific strategy plugins to account for " + "``include_role`` tasks as well.", + version="2.21" + ) display.debug("loading included file: %s" % included_file._filename) try: data = self._loader.load_from_file(included_file._filename) @@ -872,11 +886,9 @@ class StrategyBase: loader=self._loader, variable_manager=self._variable_manager, ) - - # since we skip incrementing the stats when the task result is - # first processed, we do so now for each host in the list - for host in included_file._hosts: - self._tqm._stats.increment('ok', host.name) + if handle_stats_and_callbacks: + for host in included_file._hosts: + self._tqm._stats.increment('ok', host.name) except AnsibleParserError: raise except AnsibleError as e: @@ -884,18 +896,18 @@ class StrategyBase: reason = "Could not find or access '%s' on the Ansible Controller." % to_text(e.file_name) else: reason = to_text(e) - - for r in included_file._results: - r._result['failed'] = True - - for host in included_file._hosts: - tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason)) - self._tqm._stats.increment('failures', host.name) - self._tqm.send_callback('v2_runner_on_failed', tr) + if handle_stats_and_callbacks: + for r in included_file._results: + r._result['failed'] = True + + for host in included_file._hosts: + tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason)) + self._tqm._stats.increment('failures', host.name) + self._tqm.send_callback('v2_runner_on_failed', tr) raise AnsibleError(reason) from e - # finally, send the callback and return the list of blocks loaded - self._tqm.send_callback('v2_playbook_on_include', included_file) + if handle_stats_and_callbacks: + self._tqm.send_callback('v2_playbook_on_include', included_file) display.debug("done processing included file") return block_list diff --git a/lib/ansible/plugins/strategy/debug.py b/lib/ansible/plugins/strategy/debug.py index 0965bb3..6ee294b 100644 --- a/lib/ansible/plugins/strategy/debug.py +++ b/lib/ansible/plugins/strategy/debug.py @@ -12,8 +12,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 DOCUMENTATION = ''' name: debug diff --git a/lib/ansible/plugins/strategy/free.py b/lib/ansible/plugins/strategy/free.py index 5e64ef3..6f33a68 100644 --- a/lib/ansible/plugins/strategy/free.py +++ b/lib/ansible/plugins/strategy/free.py @@ -14,9 +14,7 @@ # # 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 DOCUMENTATION = ''' name: free @@ -250,7 +248,12 @@ class StrategyModule(StrategyBase): ) else: is_handler = isinstance(included_file._task, Handler) - new_blocks = self._load_included_file(included_file, iterator=iterator, is_handler=is_handler) + new_blocks = self._load_included_file( + included_file, + iterator=iterator, + is_handler=is_handler, + handle_stats_and_callbacks=False, + ) # let PlayIterator know about any new handlers included via include_role or # import_role within include_role/include_taks @@ -258,13 +261,20 @@ class StrategyModule(StrategyBase): except AnsibleParserError: raise except AnsibleError as e: - if included_file._is_role: - # include_role does not have on_include callback so display the error - display.error(to_text(e), wrap_text=False) + display.error(to_text(e), wrap_text=False) for r in included_file._results: r._result['failed'] = True + r._result['reason'] = str(e) + self._tqm._stats.increment('failures', r._host.name) + self._tqm.send_callback('v2_runner_on_failed', r) failed_includes_hosts.add(r._host) continue + else: + # since we skip incrementing the stats when the task result is + # first processed, we do so now for each host in the list + for host in included_file._hosts: + self._tqm._stats.increment('ok', host.name) + self._tqm.send_callback('v2_playbook_on_include', included_file) for new_block in new_blocks: if is_handler: diff --git a/lib/ansible/plugins/strategy/host_pinned.py b/lib/ansible/plugins/strategy/host_pinned.py index 70f22eb..f06550f 100644 --- a/lib/ansible/plugins/strategy/host_pinned.py +++ b/lib/ansible/plugins/strategy/host_pinned.py @@ -14,9 +14,7 @@ # # 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 DOCUMENTATION = ''' name: host_pinned diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index f3b117b..29f94c4 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -14,9 +14,7 @@ # # 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 DOCUMENTATION = ''' name: linear @@ -33,7 +31,7 @@ DOCUMENTATION = ''' from ansible import constants as C from ansible.errors import AnsibleError, AnsibleAssertionError, AnsibleParserError -from ansible.executor.play_iterator import IteratingStates, FailedStates +from ansible.executor.play_iterator import IteratingStates from ansible.module_utils.common.text.converters import to_text from ansible.playbook.handler import Handler from ansible.playbook.included_file import IncludedFile @@ -293,7 +291,12 @@ class StrategyModule(StrategyBase): ) else: is_handler = isinstance(included_file._task, Handler) - new_blocks = self._load_included_file(included_file, iterator=iterator, is_handler=is_handler) + new_blocks = self._load_included_file( + included_file, + iterator=iterator, + is_handler=is_handler, + handle_stats_and_callbacks=False, + ) # let PlayIterator know about any new handlers included via include_role or # import_role within include_role/include_taks @@ -326,13 +329,19 @@ class StrategyModule(StrategyBase): except AnsibleParserError: raise except AnsibleError as e: - if included_file._is_role: - # include_role does not have on_include callback so display the error - display.error(to_text(e), wrap_text=False) + display.error(to_text(e), wrap_text=False) for r in included_file._results: r._result['failed'] = True + r._result['reason'] = str(e) + self._tqm._stats.increment('failures', r._host.name) + self._tqm.send_callback('v2_runner_on_failed', r) failed_includes_hosts.add(r._host) - continue + else: + # since we skip incrementing the stats when the task result is + # first processed, we do so now for each host in the list + for host in included_file._hosts: + self._tqm._stats.increment('ok', host.name) + self._tqm.send_callback('v2_playbook_on_include', included_file) for host in failed_includes_hosts: self._tqm._failed_hosts[host.name] = True @@ -356,25 +365,16 @@ class StrategyModule(StrategyBase): failed_hosts = [] unreachable_hosts = [] for res in results: - # execute_meta() does not set 'failed' in the TaskResult - # so we skip checking it with the meta tasks and look just at the iterator - if (res.is_failed() or res._task.action in C._ACTION_META) and iterator.is_failed(res._host): + if res.is_failed(): failed_hosts.append(res._host.name) elif res.is_unreachable(): unreachable_hosts.append(res._host.name) - # if any_errors_fatal and we had an error, mark all hosts as failed - if any_errors_fatal and (len(failed_hosts) > 0 or len(unreachable_hosts) > 0): - dont_fail_states = frozenset([IteratingStates.RESCUE, IteratingStates.ALWAYS]) + if any_errors_fatal and (failed_hosts or unreachable_hosts): for host in hosts_left: - (s, dummy) = iterator.get_next_task_for_host(host, peek=True) - # the state may actually be in a child state, use the get_active_state() - # method in the iterator to figure out the true active state - s = iterator.get_active_state(s) - if s.run_state not in dont_fail_states or \ - s.run_state == IteratingStates.RESCUE and s.fail_state & FailedStates.RESCUE != 0: + if host.name not in failed_hosts: self._tqm._failed_hosts[host.name] = True - result |= self._tqm.RUN_FAILED_BREAK_PLAY + iterator.mark_host_failed(host) display.debug("done checking for any_errors_fatal") display.debug("checking for max_fail_percentage") diff --git a/lib/ansible/plugins/terminal/__init__.py b/lib/ansible/plugins/terminal/__init__.py index 2a280a9..fe7dc31 100644 --- a/lib/ansible/plugins/terminal/__init__.py +++ b/lib/ansible/plugins/terminal/__init__.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 re @@ -85,7 +84,7 @@ class TerminalBase(ABC): This method is called right after the invoke_shell() is called from the Paramiko SSHClient instance. It provides an opportunity to setup - terminal parameters such as disbling paging for instance. + terminal parameters such as disabling paging for instance. """ pass diff --git a/lib/ansible/plugins/test/__init__.py b/lib/ansible/plugins/test/__init__.py index 1400316..b0b78d1 100644 --- a/lib/ansible/plugins/test/__init__.py +++ b/lib/ansible/plugins/test/__init__.py @@ -1,8 +1,7 @@ # (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 from ansible.plugins import AnsibleJinja2Plugin diff --git a/lib/ansible/plugins/test/change.yml b/lib/ansible/plugins/test/change.yml index 8b3dbe1..ee98fec 100644 --- a/lib/ansible/plugins/test/change.yml +++ b/lib/ansible/plugins/test/change.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [change] description: - Tests if task required changes to complete - - This test checks for the existance of a C(changed) key in the input dictionary and that it is V(True) if present + - This test checks for the existence of a C(changed) key in the input dictionary and that it is V(True) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/changed.yml b/lib/ansible/plugins/test/changed.yml index 8b3dbe1..ee98fec 100644 --- a/lib/ansible/plugins/test/changed.yml +++ b/lib/ansible/plugins/test/changed.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [change] description: - Tests if task required changes to complete - - This test checks for the existance of a C(changed) key in the input dictionary and that it is V(True) if present + - This test checks for the existence of a C(changed) key in the input dictionary and that it is V(True) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/contains.yml b/lib/ansible/plugins/test/contains.yml index 6c81a2f..7d936f2 100644 --- a/lib/ansible/plugins/test/contains.yml +++ b/lib/ansible/plugins/test/contains.yml @@ -21,7 +21,7 @@ EXAMPLES: | # as a selector - action: module=doessomething - when: lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master + when: (lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master vars: lacp_groups: - master: lacp0 diff --git a/lib/ansible/plugins/test/core.py b/lib/ansible/plugins/test/core.py index 498db0e..01e672b 100644 --- a/lib/ansible/plugins/test/core.py +++ b/lib/ansible/plugins/test/core.py @@ -15,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations import re import operator as py_operator @@ -138,7 +136,7 @@ def regex(value='', pattern='', ignorecase=False, multiline=False, match_type='s def vault_encrypted(value): - """Evaulate whether a variable is a single vault encrypted value + """Evaluate whether a variable is a single vault encrypted value .. versionadded:: 2.10 """ diff --git a/lib/ansible/plugins/test/exists.yml b/lib/ansible/plugins/test/exists.yml index 6ced0dc..331ce5c 100644 --- a/lib/ansible/plugins/test/exists.yml +++ b/lib/ansible/plugins/test/exists.yml @@ -14,7 +14,7 @@ DOCUMENTATION: EXAMPLES: | vars: - my_etc_hosts_exists: "{{ '/etc/hosts' is exist }}" + my_etc_hosts_exists: "{{ '/etc/hosts' is exists }}" list_of_local_files_to_copy_to_remote: "{{ list_of_all_possible_files | select('exists') }}" RETURN: diff --git a/lib/ansible/plugins/test/failed.yml b/lib/ansible/plugins/test/failed.yml index b8cd78b..c880f2e 100644 --- a/lib/ansible/plugins/test/failed.yml +++ b/lib/ansible/plugins/test/failed.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [failure] description: - Tests if task finished in failure, opposite of C(succeeded). - - This test checks for the existance of a C(failed) key in the input dictionary and that it is V(True) if present. + - This test checks for the existence of a C(failed) key in the input dictionary and that it is V(True) if present. - Tasks that get skipped or not executed due to other failures (syntax, templating, unreachable host, etc) do not return a 'failed' status. options: _input: diff --git a/lib/ansible/plugins/test/failure.yml b/lib/ansible/plugins/test/failure.yml index b8cd78b..c880f2e 100644 --- a/lib/ansible/plugins/test/failure.yml +++ b/lib/ansible/plugins/test/failure.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [failure] description: - Tests if task finished in failure, opposite of C(succeeded). - - This test checks for the existance of a C(failed) key in the input dictionary and that it is V(True) if present. + - This test checks for the existence of a C(failed) key in the input dictionary and that it is V(True) if present. - Tasks that get skipped or not executed due to other failures (syntax, templating, unreachable host, etc) do not return a 'failed' status. options: _input: diff --git a/lib/ansible/plugins/test/files.py b/lib/ansible/plugins/test/files.py index f075cae..fc142b7 100644 --- a/lib/ansible/plugins/test/files.py +++ b/lib/ansible/plugins/test/files.py @@ -15,9 +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/>. -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations from os.path import isdir, isfile, isabs, exists, lexists, islink, samefile, ismount diff --git a/lib/ansible/plugins/test/finished.yml b/lib/ansible/plugins/test/finished.yml index 22bd6e8..c83c5a3 100644 --- a/lib/ansible/plugins/test/finished.yml +++ b/lib/ansible/plugins/test/finished.yml @@ -4,8 +4,8 @@ DOCUMENTATION: version_added: "1.9" short_description: Did async task finish description: - - Used to test if an async task has finished, it will aslo work with normal tasks but will issue a warning. - - This test checks for the existance of a C(finished) key in the input dictionary and that it is V(1) if present + - Used to test if an async task has finished, it will also work with normal tasks but will issue a warning. + - This test checks for the existence of a C(finished) key in the input dictionary and that it is V(1) if present options: _input: description: registered result from an Ansible task @@ -17,5 +17,5 @@ EXAMPLES: | RETURN: _value: - description: Returns V(True) if the aysnc task has finished, V(False) otherwise. + description: Returns V(True) if the async task has finished, V(False) otherwise. type: boolean diff --git a/lib/ansible/plugins/test/issuperset.yml b/lib/ansible/plugins/test/issuperset.yml index 7114980..1e16b45 100644 --- a/lib/ansible/plugins/test/issuperset.yml +++ b/lib/ansible/plugins/test/issuperset.yml @@ -19,7 +19,7 @@ DOCUMENTATION: required: True EXAMPLES: | big: [1,2,3,4,5] - sml: [3,4] + small: [3,4] issmallinbig: '{{ big is superset(small) }}' RETURN: _value: diff --git a/lib/ansible/plugins/test/match.yml b/lib/ansible/plugins/test/match.yml index 76f656b..f1ffc7b 100644 --- a/lib/ansible/plugins/test/match.yml +++ b/lib/ansible/plugins/test/match.yml @@ -15,7 +15,7 @@ DOCUMENTATION: type: string required: True ignorecase: - description: Use case insenstive matching. + description: Use case insensitive matching. type: boolean default: False multiline: diff --git a/lib/ansible/plugins/test/mathstuff.py b/lib/ansible/plugins/test/mathstuff.py index 9a3f467..4bf33e8 100644 --- a/lib/ansible/plugins/test/mathstuff.py +++ b/lib/ansible/plugins/test/mathstuff.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 math diff --git a/lib/ansible/plugins/test/reachable.yml b/lib/ansible/plugins/test/reachable.yml index bddd860..3f9a01e 100644 --- a/lib/ansible/plugins/test/reachable.yml +++ b/lib/ansible/plugins/test/reachable.yml @@ -5,7 +5,7 @@ DOCUMENTATION: short_description: Task did not end due to unreachable host description: - Tests if task was able to reach the host for execution - - This test checks for the existance of a C(unreachable) key in the input dictionary and that it is V(False) if present + - This test checks for the existence of a C(unreachable) key in the input dictionary and that it is V(False) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/regex.yml b/lib/ansible/plugins/test/regex.yml index 1b2cd69..d80ca85 100644 --- a/lib/ansible/plugins/test/regex.yml +++ b/lib/ansible/plugins/test/regex.yml @@ -14,7 +14,7 @@ DOCUMENTATION: type: string required: True ignorecase: - description: Use case insenstive matching. + description: Use case insensitive matching. type: boolean default: False multiline: diff --git a/lib/ansible/plugins/test/search.yml b/lib/ansible/plugins/test/search.yml index 9a7551c..0348353 100644 --- a/lib/ansible/plugins/test/search.yml +++ b/lib/ansible/plugins/test/search.yml @@ -14,7 +14,7 @@ DOCUMENTATION: type: string required: True ignorecase: - description: Use case insenstive matching. + description: Use case insensitive matching. type: boolean default: False multiline: diff --git a/lib/ansible/plugins/test/skip.yml b/lib/ansible/plugins/test/skip.yml index 2aad3a3..808f067 100644 --- a/lib/ansible/plugins/test/skip.yml +++ b/lib/ansible/plugins/test/skip.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [skip] description: - Tests if task was skipped - - This test checks for the existance of a C(skipped) key in the input dictionary and that it is V(True) if present + - This test checks for the existence of a C(skipped) key in the input dictionary and that it is V(True) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/skipped.yml b/lib/ansible/plugins/test/skipped.yml index 2aad3a3..808f067 100644 --- a/lib/ansible/plugins/test/skipped.yml +++ b/lib/ansible/plugins/test/skipped.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [skip] description: - Tests if task was skipped - - This test checks for the existance of a C(skipped) key in the input dictionary and that it is V(True) if present + - This test checks for the existence of a C(skipped) key in the input dictionary and that it is V(True) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/started.yml b/lib/ansible/plugins/test/started.yml index 23a6cb5..34a28b6 100644 --- a/lib/ansible/plugins/test/started.yml +++ b/lib/ansible/plugins/test/started.yml @@ -5,7 +5,7 @@ DOCUMENTATION: short_description: Was async task started description: - Used to check if an async task has started, will also work with non async tasks but will issue a warning. - - This test checks for the existance of a C(started) key in the input dictionary and that it is V(1) if present + - This test checks for the existence of a C(started) key in the input dictionary and that it is V(1) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/succeeded.yml b/lib/ansible/plugins/test/succeeded.yml index 97105c8..753869f 100644 --- a/lib/ansible/plugins/test/succeeded.yml +++ b/lib/ansible/plugins/test/succeeded.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [succeeded, successful] description: - Tests if task finished successfully, opposite of C(failed). - - This test checks for the existance of a C(failed) key in the input dictionary and that it is V(False) if present + - This test checks for the existence of a C(failed) key in the input dictionary and that it is V(False) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/success.yml b/lib/ansible/plugins/test/success.yml index 97105c8..753869f 100644 --- a/lib/ansible/plugins/test/success.yml +++ b/lib/ansible/plugins/test/success.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [succeeded, successful] description: - Tests if task finished successfully, opposite of C(failed). - - This test checks for the existance of a C(failed) key in the input dictionary and that it is V(False) if present + - This test checks for the existence of a C(failed) key in the input dictionary and that it is V(False) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/successful.yml b/lib/ansible/plugins/test/successful.yml index 97105c8..753869f 100644 --- a/lib/ansible/plugins/test/successful.yml +++ b/lib/ansible/plugins/test/successful.yml @@ -6,7 +6,7 @@ DOCUMENTATION: aliases: [succeeded, successful] description: - Tests if task finished successfully, opposite of C(failed). - - This test checks for the existance of a C(failed) key in the input dictionary and that it is V(False) if present + - This test checks for the existence of a C(failed) key in the input dictionary and that it is V(False) if present options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/superset.yml b/lib/ansible/plugins/test/superset.yml index 7114980..1e16b45 100644 --- a/lib/ansible/plugins/test/superset.yml +++ b/lib/ansible/plugins/test/superset.yml @@ -19,7 +19,7 @@ DOCUMENTATION: required: True EXAMPLES: | big: [1,2,3,4,5] - sml: [3,4] + small: [3,4] issmallinbig: '{{ big is superset(small) }}' RETURN: _value: diff --git a/lib/ansible/plugins/test/unreachable.yml b/lib/ansible/plugins/test/unreachable.yml index 52e2730..018bee6 100644 --- a/lib/ansible/plugins/test/unreachable.yml +++ b/lib/ansible/plugins/test/unreachable.yml @@ -5,7 +5,7 @@ DOCUMENTATION: short_description: Did task end due to the host was unreachable description: - Tests if task was not able to reach the host for execution - - This test checks for the existance of a C(unreachable) key in the input dictionary and that it's value is V(True) + - This test checks for the existence of a C(unreachable) key in the input dictionary and that it's value is V(True) options: _input: description: registered result from an Ansible task diff --git a/lib/ansible/plugins/test/uri.py b/lib/ansible/plugins/test/uri.py index 7ef3381..b9679d0 100644 --- a/lib/ansible/plugins/test/uri.py +++ b/lib/ansible/plugins/test/uri.py @@ -1,8 +1,6 @@ # (c) Ansible Project -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type +from __future__ import annotations from urllib.parse import urlparse diff --git a/lib/ansible/plugins/vars/__init__.py b/lib/ansible/plugins/vars/__init__.py index 4f9045b..12b52d9 100644 --- a/lib/ansible/plugins/vars/__init__.py +++ b/lib/ansible/plugins/vars/__init__.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 from ansible.plugins import AnsiblePlugin from ansible.utils.path import basedir diff --git a/lib/ansible/plugins/vars/host_group_vars.py b/lib/ansible/plugins/vars/host_group_vars.py index 28b4213..cd02cc5 100644 --- a/lib/ansible/plugins/vars/host_group_vars.py +++ b/lib/ansible/plugins/vars/host_group_vars.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 DOCUMENTATION = ''' name: host_group_vars @@ -74,7 +73,7 @@ class VarsModule(BaseVarsPlugin): def load_found_files(self, loader, data, found_files): for found in found_files: - new_data = loader.load_from_file(found, cache=True, unsafe=True) + new_data = loader.load_from_file(found, cache='all', unsafe=True) if new_data: # ignore empty files data = combine_vars(data, new_data) return data |