summaryrefslogtreecommitdiff
path: root/lib/ansible/plugins/action
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/plugins/action')
-rw-r--r--lib/ansible/plugins/action/__init__.py84
-rw-r--r--lib/ansible/plugins/action/add_host.py5
-rw-r--r--lib/ansible/plugins/action/assemble.py2
-rw-r--r--lib/ansible/plugins/action/assert.py3
-rw-r--r--lib/ansible/plugins/action/async_status.py1
-rw-r--r--lib/ansible/plugins/action/command.py1
-rw-r--r--lib/ansible/plugins/action/copy.py4
-rw-r--r--lib/ansible/plugins/action/debug.py34
-rw-r--r--lib/ansible/plugins/action/fail.py1
-rw-r--r--lib/ansible/plugins/action/fetch.py4
-rw-r--r--lib/ansible/plugins/action/gather_facts.py50
-rw-r--r--lib/ansible/plugins/action/group_by.py1
-rw-r--r--lib/ansible/plugins/action/include_vars.py19
-rw-r--r--lib/ansible/plugins/action/normal.py29
-rw-r--r--lib/ansible/plugins/action/pause.py257
-rw-r--r--lib/ansible/plugins/action/reboot.py37
-rw-r--r--lib/ansible/plugins/action/script.py31
-rw-r--r--lib/ansible/plugins/action/set_fact.py1
-rw-r--r--lib/ansible/plugins/action/set_stats.py2
-rw-r--r--lib/ansible/plugins/action/shell.py6
-rw-r--r--lib/ansible/plugins/action/template.py43
-rw-r--r--lib/ansible/plugins/action/unarchive.py2
-rw-r--r--lib/ansible/plugins/action/uri.py3
-rw-r--r--lib/ansible/plugins/action/validate_argument_spec.py3
-rw-r--r--lib/ansible/plugins/action/wait_for_connection.py8
-rw-r--r--lib/ansible/plugins/action/yum.py8
26 files changed, 268 insertions, 371 deletions
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
index 8f923253..5ba3bd78 100644
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -27,7 +27,7 @@ from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
from ansible.module_utils.errors import UnsupportedError
from ansible.module_utils.json_utils import _filter_non_json_lines
from ansible.module_utils.six import binary_type, string_types, text_type
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.parsing.utils.jsonify import jsonify
from ansible.release import __version__
from ansible.utils.collection_loader import resource_from_fqcr
@@ -39,6 +39,18 @@ from ansible.utils.plugin_docs import get_versioned_doclink
display = Display()
+def _validate_utf8_json(d):
+ if isinstance(d, text_type):
+ # Purposefully not using to_bytes here for performance reasons
+ d.encode(encoding='utf-8', errors='strict')
+ elif isinstance(d, dict):
+ for o in d.items():
+ _validate_utf8_json(o)
+ elif isinstance(d, (list, tuple)):
+ for o in d:
+ _validate_utf8_json(o)
+
+
class ActionBase(ABC):
'''
@@ -51,6 +63,13 @@ class ActionBase(ABC):
# A set of valid arguments
_VALID_ARGS = frozenset([]) # type: frozenset[str]
+ # behavioral attributes
+ BYPASS_HOST_LOOP = False
+ TRANSFERS_FILES = False
+ _requires_connection = True
+ _supports_check_mode = True
+ _supports_async = False
+
def __init__(self, task, connection, play_context, loader, templar, shared_loader_obj):
self._task = task
self._connection = connection
@@ -60,20 +79,16 @@ class ActionBase(ABC):
self._shared_loader_obj = shared_loader_obj
self._cleanup_remote_tmp = False
- self._supports_check_mode = True
- self._supports_async = False
-
# interpreter discovery state
self._discovered_interpreter_key = None
self._discovered_interpreter = False
self._discovery_deprecation_warnings = []
self._discovery_warnings = []
+ self._used_interpreter = None
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display
- self._used_interpreter = None
-
@abstractmethod
def run(self, tmp=None, task_vars=None):
""" Action Plugins should implement this method to perform their
@@ -284,7 +299,8 @@ class ActionBase(ABC):
try:
(module_data, module_style, module_shebang) = modify_module(module_name, module_path, module_args, self._templar,
task_vars=use_vars,
- module_compression=self._play_context.module_compression,
+ module_compression=C.config.get_config_value('DEFAULT_MODULE_COMPRESSION',
+ variables=task_vars),
async_timeout=self._task.async_val,
environment=final_environment,
remote_is_local=bool(getattr(self._connection, '_remote_is_local', False)),
@@ -723,8 +739,7 @@ class ActionBase(ABC):
return remote_paths
# we'll need this down here
- become_link = get_versioned_doclink('playbook_guide/playbooks_privilege_escalation.html#risks-of-becoming-an-unprivileged-user')
-
+ become_link = get_versioned_doclink('playbook_guide/playbooks_privilege_escalation.html')
# Step 3f: Common group
# Otherwise, we're a normal user. We failed to chown the paths to the
# unprivileged user, but if we have a common group with them, we should
@@ -861,38 +876,6 @@ class ActionBase(ABC):
return mystat['stat']
- def _remote_checksum(self, path, all_vars, follow=False):
- """Deprecated. Use _execute_remote_stat() instead.
-
- Produces a remote checksum given a path,
- Returns a number 0-4 for specific errors instead of checksum, also ensures it is different
- 0 = unknown error
- 1 = file does not exist, this might not be an error
- 2 = permissions issue
- 3 = its a directory, not a file
- 4 = stat module failed, likely due to not finding python
- 5 = appropriate json module not found
- """
- self._display.deprecated("The '_remote_checksum()' method is deprecated. "
- "The plugin author should update the code to use '_execute_remote_stat()' instead", "2.16")
- x = "0" # unknown error has occurred
- try:
- remote_stat = self._execute_remote_stat(path, all_vars, follow=follow)
- if remote_stat['exists'] and remote_stat['isdir']:
- x = "3" # its a directory not a file
- else:
- x = remote_stat['checksum'] # if 1, file is missing
- except AnsibleError as e:
- errormsg = to_text(e)
- if errormsg.endswith(u'Permission denied'):
- x = "2" # cannot read file
- elif errormsg.endswith(u'MODULE FAILURE'):
- x = "4" # python not found or module uncaught exception
- elif 'json' in errormsg:
- x = "5" # json module needed
- finally:
- return x # pylint: disable=lost-exception
-
def _remote_expand_user(self, path, sudoable=True, pathsep=None):
''' takes a remote path and performs tilde/$HOME expansion on the remote host '''
@@ -1232,6 +1215,18 @@ class ActionBase(ABC):
display.warning(w)
data = json.loads(filtered_output)
+
+ if C.MODULE_STRICT_UTF8_RESPONSE and not data.pop('_ansible_trusted_utf8', None):
+ try:
+ _validate_utf8_json(data)
+ except UnicodeEncodeError:
+ # When removing this, also remove the loop and latin-1 from ansible.module_utils.common.text.converters.jsonify
+ display.deprecated(
+ f'Module "{self._task.resolved_action or self._task.action}" returned non UTF-8 data in '
+ 'the JSON response. This will become an error in the future',
+ version='2.18',
+ )
+
data['_ansible_parsed'] = True
except ValueError:
# not valid json, lets try to capture error
@@ -1344,7 +1339,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, source_file=True):
+ def _get_diff_data(self, destination, source, task_vars, content, 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
@@ -1402,7 +1397,10 @@ class ActionBase(ABC):
if b"\x00" in src_contents:
diff['src_binary'] = 1
else:
- diff['after_header'] = source
+ if content:
+ diff['after_header'] = destination
+ else:
+ diff['after_header'] = source
diff['after'] = to_text(src_contents)
else:
display.debug(u"source of file passed in")
diff --git a/lib/ansible/plugins/action/add_host.py b/lib/ansible/plugins/action/add_host.py
index e5697399..ede2e05f 100644
--- a/lib/ansible/plugins/action/add_host.py
+++ b/lib/ansible/plugins/action/add_host.py
@@ -37,12 +37,11 @@ class ActionModule(ActionBase):
# We need to be able to modify the inventory
BYPASS_HOST_LOOP = True
- TRANSFERS_FILES = False
+ _requires_connection = False
+ _supports_check_mode = True
def run(self, tmp=None, task_vars=None):
- self._supports_check_mode = True
-
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
diff --git a/lib/ansible/plugins/action/assemble.py b/lib/ansible/plugins/action/assemble.py
index 06fa2df3..da794edd 100644
--- a/lib/ansible/plugins/action/assemble.py
+++ b/lib/ansible/plugins/action/assemble.py
@@ -27,7 +27,7 @@ import tempfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.hashing import checksum_s
diff --git a/lib/ansible/plugins/action/assert.py b/lib/ansible/plugins/action/assert.py
index e8ab6a9a..e2fe329e 100644
--- a/lib/ansible/plugins/action/assert.py
+++ b/lib/ansible/plugins/action/assert.py
@@ -27,7 +27,8 @@ from ansible.module_utils.parsing.convert_bool import boolean
class ActionModule(ActionBase):
''' Fail with custom message '''
- TRANSFERS_FILES = False
+ _requires_connection = False
+
_VALID_ARGS = frozenset(('fail_msg', 'msg', 'quiet', 'success_msg', 'that'))
def run(self, tmp=None, task_vars=None):
diff --git a/lib/ansible/plugins/action/async_status.py b/lib/ansible/plugins/action/async_status.py
index ad839f1e..4f50fe62 100644
--- a/lib/ansible/plugins/action/async_status.py
+++ b/lib/ansible/plugins/action/async_status.py
@@ -4,7 +4,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.errors import AnsibleActionFail
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 82a85dcd..64e1a094 100644
--- a/lib/ansible/plugins/action/command.py
+++ b/lib/ansible/plugins/action/command.py
@@ -4,7 +4,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible import constants as C
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 cb3d15b3..048f98dd 100644
--- a/lib/ansible/plugins/action/copy.py
+++ b/lib/ansible/plugins/action/copy.py
@@ -30,7 +30,7 @@ import traceback
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils.basic import FILE_COMMON_ARGUMENTS
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.hashing import checksum
@@ -286,7 +286,7 @@ class ActionModule(ActionBase):
# The checksums don't match and we will change or error out.
if self._play_context.diff and not raw:
- result['diff'].append(self._get_diff_data(dest_file, source_full, task_vars))
+ result['diff'].append(self._get_diff_data(dest_file, source_full, task_vars, content))
if self._play_context.check_mode:
self._remove_tempfile_if_content_defined(content, content_tempfile)
diff --git a/lib/ansible/plugins/action/debug.py b/lib/ansible/plugins/action/debug.py
index 2584fd3d..9e23c5fa 100644
--- a/lib/ansible/plugins/action/debug.py
+++ b/lib/ansible/plugins/action/debug.py
@@ -20,7 +20,7 @@ __metaclass__ = type
from ansible.errors import AnsibleUndefinedVariable
from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.action import ActionBase
@@ -29,28 +29,34 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = False
_VALID_ARGS = frozenset(('msg', 'var', 'verbosity'))
+ _requires_connection = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
- if 'msg' in self._task.args and 'var' in self._task.args:
- return {"failed": True, "msg": "'msg' and 'var' are incompatible options"}
+ validation_result, new_module_args = self.validate_argument_spec(
+ argument_spec={
+ 'msg': {'type': 'raw', 'default': 'Hello world!'},
+ 'var': {'type': 'raw'},
+ 'verbosity': {'type': 'int', 'default': 0},
+ },
+ mutually_exclusive=(
+ ('msg', 'var'),
+ ),
+ )
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
# get task verbosity
- verbosity = int(self._task.args.get('verbosity', 0))
+ verbosity = new_module_args['verbosity']
if verbosity <= self._display.verbosity:
- if 'msg' in self._task.args:
- result['msg'] = self._task.args['msg']
-
- elif 'var' in self._task.args:
+ if new_module_args['var']:
try:
- results = self._templar.template(self._task.args['var'], convert_bare=True, fail_on_undefined=True)
- if results == self._task.args['var']:
+ results = self._templar.template(new_module_args['var'], convert_bare=True, fail_on_undefined=True)
+ if results == new_module_args['var']:
# if results is not str/unicode type, raise an exception
if not isinstance(results, string_types):
raise AnsibleUndefinedVariable
@@ -61,13 +67,13 @@ class ActionModule(ActionBase):
if self._display.verbosity > 0:
results += u": %s" % to_text(e)
- if isinstance(self._task.args['var'], (list, dict)):
+ if isinstance(new_module_args['var'], (list, dict)):
# If var is a list or dict, use the type as key to display
- result[to_text(type(self._task.args['var']))] = results
+ result[to_text(type(new_module_args['var']))] = results
else:
- result[self._task.args['var']] = results
+ result[new_module_args['var']] = results
else:
- result['msg'] = 'Hello world!'
+ result['msg'] = new_module_args['msg']
# force flag to make debug output module always verbose
result['_ansible_verbose_always'] = True
diff --git a/lib/ansible/plugins/action/fail.py b/lib/ansible/plugins/action/fail.py
index 8d3450c8..dedfc8c4 100644
--- a/lib/ansible/plugins/action/fail.py
+++ b/lib/ansible/plugins/action/fail.py
@@ -26,6 +26,7 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = False
_VALID_ARGS = frozenset(('msg',))
+ _requires_connection = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
diff --git a/lib/ansible/plugins/action/fetch.py b/lib/ansible/plugins/action/fetch.py
index 992ba5a5..11c91eb2 100644
--- a/lib/ansible/plugins/action/fetch.py
+++ b/lib/ansible/plugins/action/fetch.py
@@ -19,7 +19,7 @@ __metaclass__ = type
import os
import base64
-from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleActionSkip
+from ansible.errors import AnsibleConnectionFailure, AnsibleError, AnsibleActionFail, AnsibleActionSkip
from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.module_utils.six import string_types
from ansible.module_utils.parsing.convert_bool import boolean
@@ -75,6 +75,8 @@ class ActionModule(ActionBase):
# Follow symlinks because fetch always follows symlinks
try:
remote_stat = self._execute_remote_stat(source, all_vars=task_vars, follow=True)
+ except AnsibleConnectionFailure:
+ raise
except AnsibleError as ae:
result['changed'] = False
result['file'] = source
diff --git a/lib/ansible/plugins/action/gather_facts.py b/lib/ansible/plugins/action/gather_facts.py
index 3ff7beb5..23962c83 100644
--- a/lib/ansible/plugins/action/gather_facts.py
+++ b/lib/ansible/plugins/action/gather_facts.py
@@ -6,6 +6,7 @@ __metaclass__ = type
import os
import time
+import typing as t
from ansible import constants as C
from ansible.executor.module_common import get_action_args_with_defaults
@@ -16,12 +17,15 @@ from ansible.utils.vars import merge_hash
class ActionModule(ActionBase):
- def _get_module_args(self, fact_module, task_vars):
+ _supports_check_mode = True
+
+ def _get_module_args(self, fact_module: str, task_vars: dict[str, t.Any]) -> dict[str, t.Any]:
mod_args = self._task.args.copy()
# deal with 'setup specific arguments'
if fact_module not in C._ACTION_SETUP:
+
# TODO: remove in favor of controller side argspec detecing valid arguments
# network facts modules must support gather_subset
try:
@@ -30,16 +34,16 @@ class ActionModule(ActionBase):
name = self._connection._load_name.split('.')[-1]
if name not in ('network_cli', 'httpapi', 'netconf'):
subset = mod_args.pop('gather_subset', None)
- if subset not in ('all', ['all']):
- self._display.warning('Ignoring subset(%s) for %s' % (subset, fact_module))
+ if subset not in ('all', ['all'], None):
+ self._display.warning('Not passing subset(%s) to %s' % (subset, fact_module))
timeout = mod_args.pop('gather_timeout', None)
if timeout is not None:
- self._display.warning('Ignoring timeout(%s) for %s' % (timeout, fact_module))
+ self._display.warning('Not passing timeout(%s) to %s' % (timeout, fact_module))
fact_filter = mod_args.pop('filter', None)
if fact_filter is not None:
- self._display.warning('Ignoring filter(%s) for %s' % (fact_filter, fact_module))
+ self._display.warning('Not passing filter(%s) to %s' % (fact_filter, fact_module))
# Strip out keys with ``None`` values, effectively mimicking ``omit`` behavior
# This ensures we don't pass a ``None`` value as an argument expecting a specific type
@@ -57,7 +61,7 @@ class ActionModule(ActionBase):
return mod_args
- def _combine_task_result(self, result, task_result):
+ def _combine_task_result(self, result: dict[str, t.Any], task_result: dict[str, t.Any]) -> dict[str, t.Any]:
filtered_res = {
'ansible_facts': task_result.get('ansible_facts', {}),
'warnings': task_result.get('warnings', []),
@@ -67,9 +71,7 @@ class ActionModule(ActionBase):
# on conflict the last plugin processed wins, but try to do deep merge and append to lists.
return merge_hash(result, filtered_res, list_merge='append_rp')
- def run(self, tmp=None, task_vars=None):
-
- self._supports_check_mode = True
+ def run(self, tmp: t.Optional[str] = None, task_vars: t.Optional[dict[str, t.Any]] = None) -> dict[str, t.Any]:
result = super(ActionModule, self).run(tmp, task_vars)
result['ansible_facts'] = {}
@@ -87,16 +89,23 @@ class ActionModule(ActionBase):
failed = {}
skipped = {}
- if parallel is None and len(modules) >= 1:
- parallel = True
+ if parallel is None:
+ if len(modules) > 1:
+ parallel = True
+ else:
+ parallel = False
else:
parallel = boolean(parallel)
- if parallel:
+ timeout = self._task.args.get('gather_timeout', None)
+ async_val = self._task.async_val
+
+ if not parallel:
# serially execute each module
for fact_module in modules:
# just one module, no need for fancy async
mod_args = self._get_module_args(fact_module, task_vars)
+ # TODO: use gather_timeout to cut module execution if module itself does not support gather_timeout
res = self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=False)
if res.get('failed', False):
failed[fact_module] = res
@@ -107,10 +116,21 @@ class ActionModule(ActionBase):
self._remove_tmp_path(self._connection._shell.tmpdir)
else:
- # do it async
+ # do it async, aka parallel
jobs = {}
+
for fact_module in modules:
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?
+ if timeout and 'gather_timeout' not in mod_args:
+ self._task.async_val = int(timeout)
+ elif async_val != 0:
+ self._task.async_val = async_val
+ else:
+ self._task.async_val = 0
+
self._display.vvvv("Running %s" % fact_module)
jobs[fact_module] = (self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=True))
@@ -132,6 +152,10 @@ class ActionModule(ActionBase):
else:
time.sleep(0.5)
+ # restore value for post processing
+ if self._task.async_val != async_val:
+ self._task.async_val = async_val
+
if skipped:
result['msg'] = "The following modules were skipped: %s\n" % (', '.join(skipped.keys()))
result['skipped_modules'] = skipped
diff --git a/lib/ansible/plugins/action/group_by.py b/lib/ansible/plugins/action/group_by.py
index 0958ad80..e0c70231 100644
--- a/lib/ansible/plugins/action/group_by.py
+++ b/lib/ansible/plugins/action/group_by.py
@@ -27,6 +27,7 @@ class ActionModule(ActionBase):
# We need to be able to modify the inventory
TRANSFERS_FILES = False
_VALID_ARGS = frozenset(('key', 'parents'))
+ _requires_connection = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
diff --git a/lib/ansible/plugins/action/include_vars.py b/lib/ansible/plugins/action/include_vars.py
index 3c3cb9e1..83835b37 100644
--- a/lib/ansible/plugins/action/include_vars.py
+++ b/lib/ansible/plugins/action/include_vars.py
@@ -6,11 +6,12 @@ __metaclass__ = type
from os import path, walk
import re
+import pathlib
import ansible.constants as C
from ansible.errors import AnsibleError
from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.plugins.action import ActionBase
from ansible.utils.vars import combine_vars
@@ -23,6 +24,7 @@ class ActionModule(ActionBase):
VALID_DIR_ARGUMENTS = ['dir', 'depth', 'files_matching', 'ignore_files', 'extensions', 'ignore_unknown_extensions']
VALID_FILE_ARGUMENTS = ['file', '_raw_params']
VALID_ALL = ['name', 'hash_behaviour']
+ _requires_connection = False
def _set_dir_defaults(self):
if not self.depth:
@@ -181,16 +183,15 @@ class ActionModule(ActionBase):
alphabetical order. Do not iterate pass the set depth.
The default depth is unlimited.
"""
- current_depth = 0
- sorted_walk = list(walk(self.source_dir, onerror=self._log_walk))
+ sorted_walk = list(walk(self.source_dir, onerror=self._log_walk, followlinks=True))
sorted_walk.sort(key=lambda x: x[0])
for current_root, current_dir, current_files in sorted_walk:
- current_depth += 1
- if current_depth <= self.depth or self.depth == 0:
- current_files.sort()
- yield (current_root, current_files)
- else:
- break
+ # Depth 1 is the root, relative_to omits the root
+ current_depth = len(pathlib.Path(current_root).relative_to(self.source_dir).parts) + 1
+ if self.depth != 0 and current_depth > self.depth:
+ continue
+ current_files.sort()
+ yield (current_root, current_files)
def _ignore_file(self, filename):
""" Return True if a file matches the list of ignore_files.
diff --git a/lib/ansible/plugins/action/normal.py b/lib/ansible/plugins/action/normal.py
index cb91521a..b2212e62 100644
--- a/lib/ansible/plugins/action/normal.py
+++ b/lib/ansible/plugins/action/normal.py
@@ -24,33 +24,24 @@ from ansible.utils.vars import merge_hash
class ActionModule(ActionBase):
+ _supports_check_mode = True
+ _supports_async = True
+
def run(self, tmp=None, task_vars=None):
# individual modules might disagree but as the generic the action plugin, pass at this point.
- 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
- if not result.get('skipped'):
-
- if result.get('invocation', {}).get('module_args'):
- # avoid passing to modules in case of no_log
- # should not be set anymore but here for backwards compatibility
- del result['invocation']['module_args']
-
- # FUTURE: better to let _execute_module calculate this internally?
- wrap_async = self._task.async_val and not self._connection.has_native_async
+ wrap_async = self._task.async_val and not self._connection.has_native_async
- # do work!
- result = merge_hash(result, self._execute_module(task_vars=task_vars, wrap_async=wrap_async))
+ # do work!
+ result = merge_hash(result, self._execute_module(task_vars=task_vars, wrap_async=wrap_async))
- # hack to keep --verbose from showing all the setup module result
- # moved from setup module as now we filter out all _ansible_ from result
- # FIXME: is this still accurate with gather_facts etc, or does it need support for FQ and other names?
- if self._task.action in C._ACTION_SETUP:
- result['_ansible_verbose_override'] = True
+ # hack to keep --verbose from showing all the setup module result
+ # moved from setup module as now we filter out all _ansible_ from result
+ if self._task.action in C._ACTION_SETUP:
+ result['_ansible_verbose_override'] = True
if not wrap_async:
# remove a temporary path we created
diff --git a/lib/ansible/plugins/action/pause.py b/lib/ansible/plugins/action/pause.py
index 4c98cbbf..d306fbfa 100644
--- a/lib/ansible/plugins/action/pause.py
+++ b/lib/ansible/plugins/action/pause.py
@@ -18,92 +18,15 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import datetime
-import signal
-import sys
-import termios
import time
-import tty
-from os import (
- getpgrp,
- isatty,
- tcgetpgrp,
-)
-from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_text, to_native
-from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.errors import AnsibleError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive
+from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.action import ActionBase
from ansible.utils.display import Display
display = Display()
-try:
- import curses
- import io
-
- # Nest the try except since curses.error is not available if curses did not import
- try:
- curses.setupterm()
- HAS_CURSES = True
- except (curses.error, TypeError, io.UnsupportedOperation):
- HAS_CURSES = False
-except ImportError:
- HAS_CURSES = False
-
-MOVE_TO_BOL = b'\r'
-CLEAR_TO_EOL = b'\x1b[K'
-if HAS_CURSES:
- # curses.tigetstr() returns None in some circumstances
- MOVE_TO_BOL = curses.tigetstr('cr') or MOVE_TO_BOL
- CLEAR_TO_EOL = curses.tigetstr('el') or CLEAR_TO_EOL
-
-
-def setraw(fd, when=termios.TCSAFLUSH):
- """Put terminal into a raw mode.
-
- Copied from ``tty`` from CPython 3.11.0, and modified to not remove OPOST from OFLAG
-
- OPOST is kept to prevent an issue with multi line prompts from being corrupted now that display
- is proxied via the queue from forks. The problem is a race condition, in that we proxy the display
- over the fork, but before it can be displayed, this plugin will have continued executing, potentially
- setting stdout and stdin to raw which remove output post processing that commonly converts NL to CRLF
- """
- mode = termios.tcgetattr(fd)
- mode[tty.IFLAG] = mode[tty.IFLAG] & ~(termios.BRKINT | termios.ICRNL | termios.INPCK | termios.ISTRIP | termios.IXON)
- # mode[tty.OFLAG] = mode[tty.OFLAG] & ~(termios.OPOST)
- mode[tty.CFLAG] = mode[tty.CFLAG] & ~(termios.CSIZE | termios.PARENB)
- mode[tty.CFLAG] = mode[tty.CFLAG] | termios.CS8
- mode[tty.LFLAG] = mode[tty.LFLAG] & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG)
- mode[tty.CC][termios.VMIN] = 1
- mode[tty.CC][termios.VTIME] = 0
- termios.tcsetattr(fd, when, mode)
-
-
-class AnsibleTimeoutExceeded(Exception):
- pass
-
-
-def timeout_handler(signum, frame):
- raise AnsibleTimeoutExceeded
-
-
-def clear_line(stdout):
- stdout.write(b'\x1b[%s' % MOVE_TO_BOL)
- stdout.write(b'\x1b[%s' % CLEAR_TO_EOL)
-
-
-def is_interactive(fd=None):
- if fd is None:
- return False
-
- if isatty(fd):
- # Compare the current process group to the process group associated
- # with terminal of the given file descriptor to determine if the process
- # is running in the background.
- return getpgrp() == tcgetpgrp(fd)
- else:
- return False
-
class ActionModule(ActionBase):
''' pauses execution for a length or time, or until input is received '''
@@ -169,143 +92,57 @@ class ActionModule(ActionBase):
result['start'] = to_text(datetime.datetime.now())
result['user_input'] = b''
- stdin_fd = None
- old_settings = None
- try:
- if seconds is not None:
- if seconds < 1:
- seconds = 1
-
- # setup the alarm handler
- signal.signal(signal.SIGALRM, timeout_handler)
- signal.alarm(seconds)
+ default_input_complete = None
+ if seconds is not None:
+ if seconds < 1:
+ seconds = 1
- # show the timer and control prompts
- display.display("Pausing for %d seconds%s" % (seconds, echo_prompt))
- display.display("(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\r"),
-
- # show the prompt specified in the task
- if new_module_args['prompt']:
- display.display(prompt)
+ # show the timer and control prompts
+ display.display("Pausing for %d seconds%s" % (seconds, echo_prompt))
+ # show the prompt specified in the task
+ if new_module_args['prompt']:
+ display.display("(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\r")
else:
- display.display(prompt)
+ # corner case where enter does not continue, wait for timeout/interrupt only
+ prompt = "(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\r"
- # save the attributes on the existing (duped) stdin so
- # that we can restore them later after we set raw mode
- stdin_fd = None
- stdout_fd = None
- try:
- stdin = self._connection._new_stdin.buffer
- stdout = sys.stdout.buffer
- stdin_fd = stdin.fileno()
- stdout_fd = stdout.fileno()
- except (ValueError, AttributeError):
- # ValueError: someone is using a closed file descriptor as stdin
- # AttributeError: someone is using a null file descriptor as stdin on windoze
- stdin = None
- interactive = is_interactive(stdin_fd)
- if interactive:
- # grab actual Ctrl+C sequence
- try:
- intr = termios.tcgetattr(stdin_fd)[6][termios.VINTR]
- except Exception:
- # unsupported/not present, use default
- intr = b'\x03' # value for Ctrl+C
+ # don't complete on LF/CR; we expect a timeout/interrupt and ignore user input when a pause duration is specified
+ default_input_complete = tuple()
- # get backspace sequences
- try:
- backspace = termios.tcgetattr(stdin_fd)[6][termios.VERASE]
- except Exception:
- backspace = [b'\x7f', b'\x08']
+ # Only echo input if no timeout is specified
+ echo = seconds is None and echo
- old_settings = termios.tcgetattr(stdin_fd)
- setraw(stdin_fd)
-
- # Only set stdout to raw mode if it is a TTY. This is needed when redirecting
- # stdout to a file since a file cannot be set to raw mode.
- if isatty(stdout_fd):
- setraw(stdout_fd)
-
- # Only echo input if no timeout is specified
- if not seconds and echo:
- new_settings = termios.tcgetattr(stdin_fd)
- new_settings[3] = new_settings[3] | termios.ECHO
- termios.tcsetattr(stdin_fd, termios.TCSANOW, new_settings)
-
- # flush the buffer to make sure no previous key presses
- # are read in below
- termios.tcflush(stdin, termios.TCIFLUSH)
-
- while True:
- if not interactive:
- if seconds is None:
- display.warning("Not waiting for response to prompt as stdin is not interactive")
- if seconds is not None:
- # Give the signal handler enough time to timeout
- time.sleep(seconds + 1)
- break
-
- try:
- key_pressed = stdin.read(1)
-
- if key_pressed == intr: # value for Ctrl+C
- clear_line(stdout)
- raise KeyboardInterrupt
-
- if not seconds:
- # read key presses and act accordingly
- if key_pressed in (b'\r', b'\n'):
- clear_line(stdout)
- break
- elif key_pressed in backspace:
- # delete a character if backspace is pressed
- result['user_input'] = result['user_input'][:-1]
- clear_line(stdout)
- if echo:
- stdout.write(result['user_input'])
- stdout.flush()
- else:
- result['user_input'] += key_pressed
-
- except KeyboardInterrupt:
- signal.alarm(0)
- display.display("Press 'C' to continue the play or 'A' to abort \r"),
- if self._c_or_a(stdin):
- clear_line(stdout)
- break
-
- clear_line(stdout)
-
- raise AnsibleError('user requested abort!')
-
- except AnsibleTimeoutExceeded:
- # this is the exception we expect when the alarm signal
- # fires, so we simply ignore it to move into the cleanup
- pass
- finally:
- # cleanup and save some information
- # restore the old settings for the duped stdin stdin_fd
- if not (None in (stdin_fd, old_settings)) and isatty(stdin_fd):
- termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings)
-
- duration = time.time() - start
- result['stop'] = to_text(datetime.datetime.now())
- result['delta'] = int(duration)
-
- if duration_unit == 'minutes':
- duration = round(duration / 60.0, 2)
+ user_input = b''
+ try:
+ _user_input = display.prompt_until(prompt, private=not echo, seconds=seconds, complete_input=default_input_complete)
+ except AnsiblePromptInterrupt:
+ user_input = None
+ except AnsiblePromptNoninteractive:
+ if seconds is None:
+ display.warning("Not waiting for response to prompt as stdin is not interactive")
else:
- duration = round(duration, 2)
- result['stdout'] = "Paused for %s %s" % (duration, duration_unit)
+ # wait specified duration
+ time.sleep(seconds)
+ else:
+ if seconds is None:
+ user_input = _user_input
+ # user interrupt
+ if user_input is None:
+ prompt = "Press 'C' to continue the play or 'A' to abort \r"
+ try:
+ user_input = display.prompt_until(prompt, private=not echo, interrupt_input=(b'a',), complete_input=(b'c',))
+ except AnsiblePromptInterrupt:
+ raise AnsibleError('user requested abort!')
- result['user_input'] = to_text(result['user_input'], errors='surrogate_or_strict')
- return result
+ duration = time.time() - start
+ result['stop'] = to_text(datetime.datetime.now())
+ result['delta'] = int(duration)
- def _c_or_a(self, stdin):
- while True:
- key_pressed = stdin.read(1)
- if key_pressed.lower() == b'a':
- return False
- elif key_pressed.lower() == b'c':
- return True
+ if duration_unit == 'minutes':
+ duration = round(duration / 60.0, 2)
+ else:
+ duration = round(duration, 2)
+ result['stdout'] = "Paused for %s %s" % (duration, duration_unit)
+ result['user_input'] = to_text(user_input, errors='surrogate_or_strict')
+ return result
diff --git a/lib/ansible/plugins/action/reboot.py b/lib/ansible/plugins/action/reboot.py
index 40447d19..c75fba8e 100644
--- a/lib/ansible/plugins/action/reboot.py
+++ b/lib/ansible/plugins/action/reboot.py
@@ -8,10 +8,10 @@ __metaclass__ = type
import random
import time
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from ansible.errors import AnsibleError, AnsibleConnectionFailure
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.common.validation import check_type_list, check_type_str
from ansible.plugins.action import ActionBase
from ansible.utils.display import Display
@@ -129,7 +129,7 @@ class ActionModule(ActionBase):
else:
args = self._get_value_from_facts('SHUTDOWN_COMMAND_ARGS', distribution, 'DEFAULT_SHUTDOWN_COMMAND_ARGS')
- # Convert seconds to minutes. If less that 60, set it to 0.
+ # Convert seconds to minutes. If less than 60, set it to 0.
delay_min = self.pre_reboot_delay // 60
reboot_message = self._task.args.get('msg', self.DEFAULT_REBOOT_MESSAGE)
return args.format(delay_sec=self.pre_reboot_delay, delay_min=delay_min, message=reboot_message)
@@ -236,7 +236,7 @@ class ActionModule(ActionBase):
display.vvv("{action}: attempting to get system boot time".format(action=self._task.action))
connect_timeout = self._task.args.get('connect_timeout', self._task.args.get('connect_timeout_sec', self.DEFAULT_CONNECT_TIMEOUT))
- # override connection timeout from defaults to custom value
+ # override connection timeout from defaults to the custom value
if connect_timeout:
try:
display.debug("{action}: setting connect_timeout to {value}".format(action=self._task.action, value=connect_timeout))
@@ -280,14 +280,15 @@ class ActionModule(ActionBase):
display.vvv("{action}: system successfully rebooted".format(action=self._task.action))
def do_until_success_or_timeout(self, action, reboot_timeout, action_desc, distribution, action_kwargs=None):
- max_end_time = datetime.utcnow() + timedelta(seconds=reboot_timeout)
+ max_end_time = datetime.now(timezone.utc) + timedelta(seconds=reboot_timeout)
if action_kwargs is None:
action_kwargs = {}
fail_count = 0
max_fail_sleep = 12
+ last_error_msg = ''
- while datetime.utcnow() < max_end_time:
+ while datetime.now(timezone.utc) < max_end_time:
try:
action(distribution=distribution, **action_kwargs)
if action_desc:
@@ -299,7 +300,7 @@ class ActionModule(ActionBase):
self._connection.reset()
except AnsibleConnectionFailure:
pass
- # Use exponential backoff with a max timout, plus a little bit of randomness
+ # Use exponential backoff with a max timeout, plus a little bit of randomness
random_int = random.randint(0, 1000) / 1000
fail_sleep = 2 ** fail_count + random_int
if fail_sleep > max_fail_sleep:
@@ -310,14 +311,18 @@ class ActionModule(ActionBase):
error = to_text(e).splitlines()[-1]
except IndexError as e:
error = to_text(e)
- display.debug("{action}: {desc} fail '{err}', retrying in {sleep:.4} seconds...".format(
- action=self._task.action,
- desc=action_desc,
- err=error,
- sleep=fail_sleep))
+ last_error_msg = f"{self._task.action}: {action_desc} fail '{error}'"
+ msg = f"{last_error_msg}, retrying in {fail_sleep:.4f} seconds..."
+
+ display.debug(msg)
+ display.vvv(msg)
fail_count += 1
time.sleep(fail_sleep)
+ if last_error_msg:
+ msg = f"Last error message before the timeout exception - {last_error_msg}"
+ display.debug(msg)
+ display.vvv(msg)
raise TimedOutException('Timed out waiting for {desc} (timeout={timeout})'.format(desc=action_desc, timeout=reboot_timeout))
def perform_reboot(self, task_vars, distribution):
@@ -336,7 +341,7 @@ class ActionModule(ActionBase):
display.debug('{action}: AnsibleConnectionFailure caught and handled: {error}'.format(action=self._task.action, error=to_text(e)))
reboot_result['rc'] = 0
- result['start'] = datetime.utcnow()
+ result['start'] = datetime.now(timezone.utc)
if reboot_result['rc'] != 0:
result['failed'] = True
@@ -406,7 +411,7 @@ class ActionModule(ActionBase):
self._supports_check_mode = True
self._supports_async = True
- # If running with local connection, fail so we don't reboot ourself
+ # 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}
@@ -447,7 +452,7 @@ class ActionModule(ActionBase):
if reboot_result['failed']:
result = reboot_result
- elapsed = datetime.utcnow() - reboot_result['start']
+ elapsed = datetime.now(timezone.utc) - reboot_result['start']
result['elapsed'] = elapsed.seconds
return result
@@ -459,7 +464,7 @@ class ActionModule(ActionBase):
# Make sure reboot was successful
result = self.validate_reboot(distribution, original_connection_timeout, action_kwargs={'previous_boot_time': previous_boot_time})
- elapsed = datetime.utcnow() - reboot_result['start']
+ elapsed = datetime.now(timezone.utc) - reboot_result['start']
result['elapsed'] = elapsed.seconds
return result
diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py
index 1bbb8001..e6ebd094 100644
--- a/lib/ansible/plugins/action/script.py
+++ b/lib/ansible/plugins/action/script.py
@@ -23,7 +23,7 @@ import shlex
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail, AnsibleActionSkip
from ansible.executor.powershell import module_manifest as ps_manifest
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.plugins.action import ActionBase
@@ -40,11 +40,25 @@ class ActionModule(ActionBase):
if task_vars is None:
task_vars = dict()
+ validation_result, new_module_args = self.validate_argument_spec(
+ argument_spec={
+ '_raw_params': {},
+ 'cmd': {'type': 'str'},
+ 'creates': {'type': 'str'},
+ 'removes': {'type': 'str'},
+ 'chdir': {'type': 'str'},
+ 'executable': {'type': 'str'},
+ },
+ required_one_of=[
+ ['_raw_params', 'cmd']
+ ]
+ )
+
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
try:
- creates = self._task.args.get('creates')
+ creates = new_module_args['creates']
if creates:
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
@@ -52,7 +66,7 @@ class ActionModule(ActionBase):
if self._remote_file_exists(creates):
raise AnsibleActionSkip("%s exists, matching creates option" % creates)
- removes = self._task.args.get('removes')
+ removes = new_module_args['removes']
if removes:
# do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence
@@ -62,7 +76,7 @@ class ActionModule(ActionBase):
# The chdir must be absolute, because a relative path would rely on
# remote node behaviour & user config.
- chdir = self._task.args.get('chdir')
+ chdir = new_module_args['chdir']
if chdir:
# Powershell is the only Windows-path aware shell
if getattr(self._connection._shell, "_IS_WINDOWS", False) and \
@@ -75,13 +89,14 @@ class ActionModule(ActionBase):
# Split out the script as the first item in raw_params using
# shlex.split() in order to support paths and files with spaces in the name.
# Any arguments passed to the script will be added back later.
- raw_params = to_native(self._task.args.get('_raw_params', ''), errors='surrogate_or_strict')
+ raw_params = to_native(new_module_args.get('_raw_params', ''), errors='surrogate_or_strict')
parts = [to_text(s, errors='surrogate_or_strict') for s in shlex.split(raw_params.strip())]
source = parts[0]
# Support executable paths and files with spaces in the name.
- executable = to_native(self._task.args.get('executable', ''), errors='surrogate_or_strict')
-
+ executable = new_module_args['executable']
+ if executable:
+ executable = to_native(new_module_args['executable'], errors='surrogate_or_strict')
try:
source = self._loader.get_real_file(self._find_needle('files', source), decrypt=self._task.args.get('decrypt', True))
except AnsibleError as e:
@@ -90,7 +105,7 @@ class ActionModule(ActionBase):
if self._task.check_mode:
# check mode is supported if 'creates' or 'removes' are provided
# the task has already been skipped if a change would not occur
- if self._task.args.get('creates') or self._task.args.get('removes'):
+ if new_module_args['creates'] or new_module_args['removes']:
result['changed'] = True
raise _AnsibleActionDone(result=result)
# If the script doesn't return changed in the result, it defaults to True,
diff --git a/lib/ansible/plugins/action/set_fact.py b/lib/ansible/plugins/action/set_fact.py
index ae92de80..ee3ceb28 100644
--- a/lib/ansible/plugins/action/set_fact.py
+++ b/lib/ansible/plugins/action/set_fact.py
@@ -30,6 +30,7 @@ import ansible.constants as C
class ActionModule(ActionBase):
TRANSFERS_FILES = False
+ _requires_connection = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
diff --git a/lib/ansible/plugins/action/set_stats.py b/lib/ansible/plugins/action/set_stats.py
index 9d429ced..5c4f0055 100644
--- a/lib/ansible/plugins/action/set_stats.py
+++ b/lib/ansible/plugins/action/set_stats.py
@@ -18,7 +18,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.module_utils.six import string_types
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.vars import isidentifier
@@ -28,6 +27,7 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = False
_VALID_ARGS = frozenset(('aggregate', 'data', 'per_host'))
+ _requires_connection = False
# TODO: document this in non-empty set_stats.py module
def run(self, tmp=None, task_vars=None):
diff --git a/lib/ansible/plugins/action/shell.py b/lib/ansible/plugins/action/shell.py
index 617a373d..dd4df461 100644
--- a/lib/ansible/plugins/action/shell.py
+++ b/lib/ansible/plugins/action/shell.py
@@ -4,6 +4,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+from ansible.errors import AnsibleActionFail
from ansible.plugins.action import ActionBase
@@ -15,6 +16,11 @@ class ActionModule(ActionBase):
# Shell module is implemented via command with a special arg
self._task.args['_uses_shell'] = True
+ # Shell shares the same module code as command. Fail if command
+ # specific options are set.
+ if "expand_argument_vars" in self._task.args:
+ raise AnsibleActionFail(f"Unsupported parameters for ({self._task.action}) module: expand_argument_vars")
+
command_action = self._shared_loader_obj.action_loader.get('ansible.legacy.command',
task=self._task,
connection=self._connection,
diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py
index d2b3df9a..4bfd9670 100644
--- a/lib/ansible/plugins/action/template.py
+++ b/lib/ansible/plugins/action/template.py
@@ -10,10 +10,19 @@ import shutil
import stat
import tempfile
+from jinja2.defaults import (
+ BLOCK_END_STRING,
+ BLOCK_START_STRING,
+ COMMENT_END_STRING,
+ COMMENT_START_STRING,
+ VARIABLE_END_STRING,
+ VARIABLE_START_STRING,
+)
+
from ansible import constants as C
from ansible.config.manager import ensure_type
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail
-from ansible.module_utils._text import to_bytes, to_text, to_native
+from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import string_types
from ansible.plugins.action import ActionBase
@@ -57,12 +66,12 @@ class ActionModule(ActionBase):
dest = self._task.args.get('dest', None)
state = self._task.args.get('state', None)
newline_sequence = self._task.args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE)
- variable_start_string = self._task.args.get('variable_start_string', None)
- variable_end_string = self._task.args.get('variable_end_string', None)
- block_start_string = self._task.args.get('block_start_string', None)
- block_end_string = self._task.args.get('block_end_string', None)
- comment_start_string = self._task.args.get('comment_start_string', None)
- comment_end_string = self._task.args.get('comment_end_string', None)
+ variable_start_string = self._task.args.get('variable_start_string', VARIABLE_START_STRING)
+ variable_end_string = self._task.args.get('variable_end_string', VARIABLE_END_STRING)
+ block_start_string = self._task.args.get('block_start_string', BLOCK_START_STRING)
+ block_end_string = self._task.args.get('block_end_string', BLOCK_END_STRING)
+ comment_start_string = self._task.args.get('comment_start_string', COMMENT_START_STRING)
+ comment_end_string = self._task.args.get('comment_end_string', COMMENT_END_STRING)
output_encoding = self._task.args.get('output_encoding', 'utf-8') or 'utf-8'
wrong_sequences = ["\\n", "\\r", "\\r\\n"]
@@ -129,16 +138,18 @@ class ActionModule(ActionBase):
templar = self._templar.copy_with_new_env(environment_class=AnsibleEnvironment,
searchpath=searchpath,
newline_sequence=newline_sequence,
- block_start_string=block_start_string,
- block_end_string=block_end_string,
- variable_start_string=variable_start_string,
- variable_end_string=variable_end_string,
- comment_start_string=comment_start_string,
- comment_end_string=comment_end_string,
- trim_blocks=trim_blocks,
- lstrip_blocks=lstrip_blocks,
available_variables=temp_vars)
- resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)
+ overrides = dict(
+ block_start_string=block_start_string,
+ block_end_string=block_end_string,
+ variable_start_string=variable_start_string,
+ variable_end_string=variable_end_string,
+ comment_start_string=comment_start_string,
+ comment_end_string=comment_end_string,
+ trim_blocks=trim_blocks,
+ lstrip_blocks=lstrip_blocks
+ )
+ resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, overrides=overrides)
except AnsibleAction:
raise
except Exception as e:
diff --git a/lib/ansible/plugins/action/unarchive.py b/lib/ansible/plugins/action/unarchive.py
index 4d188e3d..9bce1227 100644
--- a/lib/ansible/plugins/action/unarchive.py
+++ b/lib/ansible/plugins/action/unarchive.py
@@ -21,7 +21,7 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleError, AnsibleAction, AnsibleActionFail, AnsibleActionSkip
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
diff --git a/lib/ansible/plugins/action/uri.py b/lib/ansible/plugins/action/uri.py
index bbaf092e..ffd1c89a 100644
--- a/lib/ansible/plugins/action/uri.py
+++ b/lib/ansible/plugins/action/uri.py
@@ -10,10 +10,9 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.collections import Mapping, MutableMapping
from ansible.module_utils.parsing.convert_bool import boolean
-from ansible.module_utils.six import text_type
from ansible.plugins.action import ActionBase
diff --git a/lib/ansible/plugins/action/validate_argument_spec.py b/lib/ansible/plugins/action/validate_argument_spec.py
index dc7d6cb3..b2c1d7b5 100644
--- a/lib/ansible/plugins/action/validate_argument_spec.py
+++ b/lib/ansible/plugins/action/validate_argument_spec.py
@@ -6,9 +6,7 @@ __metaclass__ = type
from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase
-from ansible.module_utils.six import string_types
from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
-from ansible.module_utils.errors import AnsibleValidationErrorMultiple
from ansible.utils.vars import combine_vars
@@ -16,6 +14,7 @@ class ActionModule(ActionBase):
''' Validate an arg spec'''
TRANSFERS_FILES = False
+ _requires_connection = False
def get_args_from_task_vars(self, argument_spec, task_vars):
'''
diff --git a/lib/ansible/plugins/action/wait_for_connection.py b/lib/ansible/plugins/action/wait_for_connection.py
index 8489c767..df549d94 100644
--- a/lib/ansible/plugins/action/wait_for_connection.py
+++ b/lib/ansible/plugins/action/wait_for_connection.py
@@ -20,9 +20,9 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import time
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.action import ActionBase
from ansible.utils.display import Display
@@ -43,10 +43,10 @@ class ActionModule(ActionBase):
DEFAULT_TIMEOUT = 600
def do_until_success_or_timeout(self, what, timeout, connect_timeout, what_desc, sleep=1):
- max_end_time = datetime.utcnow() + timedelta(seconds=timeout)
+ max_end_time = datetime.now(timezone.utc) + timedelta(seconds=timeout)
e = None
- while datetime.utcnow() < max_end_time:
+ while datetime.now(timezone.utc) < max_end_time:
try:
what(connect_timeout)
if what_desc:
diff --git a/lib/ansible/plugins/action/yum.py b/lib/ansible/plugins/action/yum.py
index d90a9e00..9121e812 100644
--- a/lib/ansible/plugins/action/yum.py
+++ b/lib/ansible/plugins/action/yum.py
@@ -23,7 +23,7 @@ from ansible.utils.display import Display
display = Display()
-VALID_BACKENDS = frozenset(('yum', 'yum4', 'dnf'))
+VALID_BACKENDS = frozenset(('yum', 'yum4', 'dnf', 'dnf4', 'dnf5'))
class ActionModule(ActionBase):
@@ -53,6 +53,9 @@ class ActionModule(ActionBase):
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
@@ -81,7 +84,7 @@ class ActionModule(ActionBase):
)
else:
- if module == "yum4":
+ if module in {"yum4", "dnf4"}:
module = "dnf"
# eliminate collisions with collections search while still allowing local override
@@ -90,7 +93,6 @@ class ActionModule(ActionBase):
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:
- # run either the yum (yum3) or dnf (yum4) backend module
new_module_args = self._task.args.copy()
if 'use_backend' in new_module_args:
del new_module_args['use_backend']