From da60525610a384bb04833b1c6429d9db6a87ef64 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 3 Nov 2020 14:51:31 +0100 Subject: [PATCH] Fix missing ansible.builtin FQCNs in hardcoded action names (#71824) * Make sure hard-coded action names also check for FQCN. * Use _add_internal_fqcn() to avoid hardcoded lists and typoes. --- changelogs/fragments/71824-action-fqcns.yml | 2 + lib/ansible/cli/adhoc.py | 4 +- lib/ansible/cli/console.py | 2 +- lib/ansible/cli/playbook.py | 3 +- lib/ansible/constants.py | 33 +++- lib/ansible/executor/task_executor.py | 8 +- lib/ansible/executor/task_result.py | 2 +- lib/ansible/parsing/mod_args.py | 9 +- lib/ansible/playbook/__init__.py | 10 +- lib/ansible/playbook/block.py | 5 +- lib/ansible/playbook/helpers.py | 18 +- lib/ansible/playbook/included_file.py | 5 +- lib/ansible/playbook/playbook_include.py | 3 +- lib/ansible/playbook/role_include.py | 5 +- lib/ansible/playbook/task.py | 10 +- lib/ansible/playbook/task_include.py | 8 +- lib/ansible/plugins/callback/__init__.py | 2 +- lib/ansible/plugins/strategy/__init__.py | 7 +- lib/ansible/plugins/strategy/free.py | 2 +- lib/ansible/plugins/strategy/linear.py | 5 +- lib/ansible/utils/fqcn.py | 33 ++++ lib/ansible/vars/manager.py | 2 +- test/integration/targets/debug/main_fqcn.yml | 6 + test/integration/targets/debug/runme.sh | 6 + .../targets/include_import/runme.sh | 7 + .../test_copious_include_tasks_fqcn.yml | 44 +++++ .../test_grandparent_inheritance_fqcn.yml | 29 ++++ .../include_import/test_include_loop_fqcn.yml | 17 ++ .../include_import/test_nested_tasks_fqcn.yml | 6 + .../test_role_recursion_fqcn.yml | 7 + .../targets/include_vars/tasks/main.yml | 18 +- .../vars/services/service_vars_fqcn.yml | 3 + .../test_includes_free/tasks/inner_fqcn.yml | 2 + .../roles/test_includes_free/tasks/main.yml | 3 + .../targets/includes/test_include_free.yml | 1 + test/integration/targets/meta_tasks/runme.sh | 20 +++ .../meta_tasks/test_end_host_all_fqcn.yml | 13 ++ .../targets/meta_tasks/test_end_host_fqcn.yml | 14 ++ .../targets/meta_tasks/test_end_play_fqcn.yml | 12 ++ .../targets/set_fact/incremental.yml | 16 ++ .../targets/set_fact/nowarn_clean_facts.yml | 3 + .../targets/set_fact/set_fact_bool_conv.yml | 15 ++ .../set_fact_bool_conv_jinja2_native.yml | 15 ++ .../targets/set_fact/set_fact_cached_1.yml | 155 +++++++++++++++++- .../targets/set_fact/set_fact_cached_2.yml | 27 +++ .../targets/set_fact/set_fact_no_cache.yml | 18 ++ 46 files changed, 571 insertions(+), 64 deletions(-) create mode 100644 changelogs/fragments/71824-action-fqcns.yml create mode 100644 lib/ansible/utils/fqcn.py create mode 100644 test/integration/targets/debug/main_fqcn.yml create mode 100644 test/integration/targets/include_import/test_copious_include_tasks_fqcn.yml create mode 100644 test/integration/targets/include_import/test_grandparent_inheritance_fqcn.yml create mode 100644 test/integration/targets/include_import/test_include_loop_fqcn.yml create mode 100644 test/integration/targets/include_import/test_nested_tasks_fqcn.yml create mode 100644 test/integration/targets/include_import/test_role_recursion_fqcn.yml create mode 100644 test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml create mode 100644 test/integration/targets/includes/roles/test_includes_free/tasks/inner_fqcn.yml create mode 100644 test/integration/targets/meta_tasks/test_end_host_all_fqcn.yml create mode 100644 test/integration/targets/meta_tasks/test_end_host_fqcn.yml create mode 100644 test/integration/targets/meta_tasks/test_end_play_fqcn.yml diff --git a/changelogs/fragments/71824-action-fqcns.yml b/changelogs/fragments/71824-action-fqcns.yml new file mode 100644 index 00000000000..e2c8729d7cd --- /dev/null +++ b/changelogs/fragments/71824-action-fqcns.yml @@ -0,0 +1,2 @@ +bugfixes: +- "Adjust various hard-coded action names to also include their ``ansible.builtin.`` and ``ansible.legacy.`` prefixed version (https://github.com/ansible/ansible/issues/71817, https://github.com/ansible/ansible/issues/71818, https://github.com/ansible/ansible/pull/71824)." diff --git a/lib/ansible/cli/adhoc.py b/lib/ansible/cli/adhoc.py index 2090721d018..ddf5a8e5344 100644 --- a/lib/ansible/cli/adhoc.py +++ b/lib/ansible/cli/adhoc.py @@ -71,7 +71,7 @@ class AdHocCLI(CLI): 'timeout': context.CLIARGS['task_timeout']} # avoid adding to tasks that don't support it, unless set, then give user an error - if context.CLIARGS['module_name'] not in ('include_role', 'include_tasks') and any(frozenset((async_val, poll))): + if context.CLIARGS['module_name'] not in C._ACTION_ALL_INCLUDE_ROLE_TASKS and any(frozenset((async_val, poll))): mytask['async_val'] = async_val mytask['poll'] = poll @@ -120,7 +120,7 @@ class AdHocCLI(CLI): raise AnsibleOptionsError(err) # Avoid modules that don't work with ad-hoc - if context.CLIARGS['module_name'] in ('import_playbook',): + if context.CLIARGS['module_name'] in C._ACTION_IMPORT_PLAYBOOK: raise AnsibleOptionsError("'%s' is not a valid action for ad-hoc commands" % context.CLIARGS['module_name']) diff --git a/lib/ansible/cli/console.py b/lib/ansible/cli/console.py index 1be9bd51cd0..7f3d69af96c 100644 --- a/lib/ansible/cli/console.py +++ b/lib/ansible/cli/console.py @@ -185,7 +185,7 @@ class ConsoleCLI(CLI, cmd.Cmd): result = None try: - check_raw = module in ('command', 'shell', 'script', 'raw') + check_raw = module in C._ACTION_ALLOWS_RAW_ARGS task = dict(action=dict(module=module, args=parse_kv(module_args, check_raw=check_raw)), timeout=self.task_timeout) play_ds = dict( name="Ansible Shell", diff --git a/lib/ansible/cli/playbook.py b/lib/ansible/cli/playbook.py index fb238146e07..a3890df18a2 100644 --- a/lib/ansible/cli/playbook.py +++ b/lib/ansible/cli/playbook.py @@ -8,6 +8,7 @@ __metaclass__ = type import os import stat +from ansible import constants as C from ansible import context from ansible.cli import CLI from ansible.cli.arguments import option_helpers as opt_help @@ -170,7 +171,7 @@ class PlaybookCLI(CLI): if isinstance(task, Block): taskmsg += _process_block(task) else: - if task.action == 'meta' and task.implicit: + if task.action in C._ACTION_META and task.implicit: continue all_tags.update(task.tags) diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index f93986ab4fc..39921896e5f 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -17,6 +17,7 @@ from ansible.module_utils._text import to_text from ansible.module_utils.common.collections import Sequence from ansible.module_utils.parsing.convert_bool import boolean, BOOLEANS_TRUE from ansible.module_utils.six import string_types +from ansible.utils.fqcn import add_internal_fqcns def _warning(msg): @@ -74,10 +75,10 @@ DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy') IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search INTERNAL_RESULT_KEYS = ('add_host', 'add_group') LOCALHOST = ('127.0.0.1', 'localhost', '::1') -MODULE_REQUIRE_ARGS = ('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', - 'ansible.windows.win_shell', 'raw', 'script') -MODULE_NO_JSON = ('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', - 'ansible.windows.win_shell', 'raw') +MODULE_REQUIRE_ARGS = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', + 'ansible.windows.win_shell', 'raw', 'script'))) +MODULE_NO_JSON = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', + 'ansible.windows.win_shell', 'raw'))) RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python', 'ansible_facts') TREE_DIR = None VAULT_VERSION_MIN = 1.0 @@ -164,3 +165,27 @@ for setting in config.data.get_settings(): for warn in config.WARNINGS: _warning(warn) + + +# The following are hard-coded action names +_ACTION_DEBUG = add_internal_fqcns(('debug', )) +_ACTION_IMPORT_PLAYBOOK = add_internal_fqcns(('import_playbook', )) +_ACTION_IMPORT_ROLE = add_internal_fqcns(('import_role', )) +_ACTION_IMPORT_TASKS = add_internal_fqcns(('import_tasks', )) +_ACTION_INCLUDE = add_internal_fqcns(('include', )) +_ACTION_INCLUDE_ROLE = add_internal_fqcns(('include_role', )) +_ACTION_INCLUDE_TASKS = add_internal_fqcns(('include_tasks', )) +_ACTION_INCLUDE_VARS = add_internal_fqcns(('include_vars', )) +_ACTION_META = add_internal_fqcns(('meta', )) +_ACTION_SET_FACT = add_internal_fqcns(('set_fact', )) +_ACTION_HAS_CMD = add_internal_fqcns(('command', 'shell', 'script')) +_ACTION_ALLOWS_RAW_ARGS = _ACTION_HAS_CMD + add_internal_fqcns(('raw', )) +_ACTION_ALL_INCLUDES = _ACTION_INCLUDE + _ACTION_INCLUDE_TASKS + _ACTION_INCLUDE_ROLE +_ACTION_ALL_IMPORT_PLAYBOOKS = _ACTION_INCLUDE + _ACTION_IMPORT_PLAYBOOK +_ACTION_ALL_INCLUDE_IMPORT_TASKS = _ACTION_INCLUDE + _ACTION_INCLUDE_TASKS + _ACTION_IMPORT_TASKS +_ACTION_ALL_PROPER_INCLUDE_IMPORT_ROLES = _ACTION_INCLUDE_ROLE + _ACTION_IMPORT_ROLE +_ACTION_ALL_PROPER_INCLUDE_IMPORT_TASKS = _ACTION_INCLUDE_TASKS + _ACTION_IMPORT_TASKS +_ACTION_ALL_INCLUDE_ROLE_TASKS = _ACTION_INCLUDE_ROLE + _ACTION_INCLUDE_TASKS +_ACTION_ALL_INCLUDE_TASKS = _ACTION_INCLUDE + _ACTION_INCLUDE_TASKS +_ACTION_FACT_GATHERING = add_internal_fqcns(('setup', 'gather_facts')) +_ACTION_WITH_CLEAN_FACTS = _ACTION_SET_FACT + _ACTION_INCLUDE_VARS diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index a0f916e602f..4c5e17595c3 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -477,7 +477,7 @@ class TaskExecutor: # if this task is a TaskInclude, we just return now with a success code so the # main thread can expand the task list for the given host - if self._task.action in ('include', 'include_tasks'): + if self._task.action in C._ACTION_ALL_INCLUDE_TASKS: include_args = self._task.args.copy() include_file = include_args.pop('_raw_params', None) if not include_file: @@ -487,7 +487,7 @@ class TaskExecutor: return dict(include=include_file, include_args=include_args) # if this task is a IncludeRole, we just return now with a success code so the main thread can expand the task list for the given host - elif self._task.action == 'include_role': + elif self._task.action in C._ACTION_INCLUDE_ROLE: include_args = self._task.args.copy() return dict(include_args=include_args) @@ -624,7 +624,7 @@ class TaskExecutor: return failed_when_result if 'ansible_facts' in result: - if self._task.action in ('set_fact', 'include_vars'): + if self._task.action in C._ACTION_WITH_CLEAN_FACTS: vars_copy.update(result['ansible_facts']) else: # TODO: cleaning of facts should eventually become part of taskresults instead of vars @@ -688,7 +688,7 @@ class TaskExecutor: variables[self._task.register] = result = wrap_var(result) if 'ansible_facts' in result: - if self._task.action in ('set_fact', 'include_vars'): + if self._task.action in C._ACTION_WITH_CLEAN_FACTS: variables.update(result['ansible_facts']) else: # TODO: cleaning of facts should eventually become part of taskresults instead of vars diff --git a/lib/ansible/executor/task_result.py b/lib/ansible/executor/task_result.py index eba18fa337d..543b860ebe7 100644 --- a/lib/ansible/executor/task_result.py +++ b/lib/ansible/executor/task_result.py @@ -113,7 +113,7 @@ class TaskResult: result = TaskResult(self._host, self._task, {}, self._task_fields) # statuses are already reflected on the event type - if result._task and result._task.action in ['debug']: + if result._task and result._task.action in C._ACTION_DEBUG: # debug is verbose by default to display vars, no need to add invocation ignore = _IGNORE + ('invocation',) else: diff --git a/lib/ansible/parsing/mod_args.py b/lib/ansible/parsing/mod_args.py index b81e81b0721..96e6b8570fc 100644 --- a/lib/ansible/parsing/mod_args.py +++ b/lib/ansible/parsing/mod_args.py @@ -26,13 +26,14 @@ from ansible.module_utils._text import to_text from ansible.parsing.splitter import parse_kv, split_args from ansible.plugins.loader import module_loader, action_loader from ansible.template import Templar +from ansible.utils.fqcn import add_internal_fqcns from ansible.utils.sentinel import Sentinel # For filtering out modules correctly below FREEFORM_ACTIONS = frozenset(C.MODULE_REQUIRE_ARGS) -RAW_PARAM_MODULES = FREEFORM_ACTIONS.union(( +RAW_PARAM_MODULES = FREEFORM_ACTIONS.union(add_internal_fqcns(( 'include', 'include_vars', 'include_tasks', @@ -43,16 +44,16 @@ RAW_PARAM_MODULES = FREEFORM_ACTIONS.union(( 'group_by', 'set_fact', 'meta', -)) +))) -BUILTIN_TASKS = frozenset(( +BUILTIN_TASKS = frozenset(add_internal_fqcns(( 'meta', 'include', 'include_tasks', 'include_role', 'import_tasks', 'import_role' -)) +))) class ModuleArgsParser: diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index c7dab6b4d1b..8c4ed65f0b3 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -91,15 +91,19 @@ class Playbook: self._loader.set_basedir(cur_basedir) raise AnsibleParserError("playbook entries must be either a valid play or an include statement", obj=entry) - if any(action in entry for action in ('import_playbook', 'include')): - if 'include' in entry: + if any(action in entry for action in C._ACTION_ALL_IMPORT_PLAYBOOKS): + if any(action in entry for action in C._ACTION_INCLUDE): display.deprecated("'include' for playbook includes. You should use 'import_playbook' instead", version="2.12", collection_name='ansible.builtin') pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader) if pb is not None: self._entries.extend(pb._entries) else: - which = entry.get('import_playbook', entry.get('include', entry)) + which = entry + for k in C._ACTION_IMPORT_PLAYBOOK + C._ACTION_INCLUDE: + if k in entry: + which = entry[k] + break display.display("skipping playbook '%s' due to conditional test failure" % which, color=C.COLOR_SKIP) else: entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader, vars=vars) diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py index cab89dd9925..5e4fc903555 100644 --- a/lib/ansible/playbook/block.py +++ b/lib/ansible/playbook/block.py @@ -19,6 +19,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import ansible.constants as C from ansible.errors import AnsibleParserError from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base @@ -374,8 +375,8 @@ class Block(Base, Conditional, CollectionSearch, Taggable): filtered_block = evaluate_block(task) if filtered_block.has_tasks(): tmp_list.append(filtered_block) - elif ((task.action == 'meta' and task.implicit) or - (task.action == 'include' and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) or + elif ((task.action in C._ACTION_META and task.implicit) or + (task.action in C._ACTION_INCLUDE and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) or task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)): tmp_list.append(task) return tmp_list diff --git a/lib/ansible/playbook/helpers.py b/lib/ansible/playbook/helpers.py index 45ff837f83d..892ce158087 100644 --- a/lib/ansible/playbook/helpers.py +++ b/lib/ansible/playbook/helpers.py @@ -128,7 +128,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h # But if it wasn't, we can add the yaml object now to get more detail raise AnsibleParserError(to_native(e), obj=task_ds, orig_exc=e) - if action in ('include', 'import_tasks', 'include_tasks'): + if action in C._ACTION_ALL_INCLUDE_IMPORT_TASKS: if use_handlers: include_class = HandlerTaskInclude @@ -150,9 +150,9 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h # check to see if this include is dynamic or static: # 1. the user has set the 'static' option to false or true # 2. one of the appropriate config options was set - if action == 'include_tasks': + if action in C._ACTION_INCLUDE_TASKS: is_static = False - elif action == 'import_tasks': + elif action in C._ACTION_IMPORT_TASKS: is_static = True elif t.static is not None: display.deprecated("The use of 'static' has been deprecated. " @@ -166,7 +166,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h if is_static: if t.loop is not None: - if action == 'import_tasks': + if action in C._ACTION_IMPORT_TASKS: raise AnsibleParserError("You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.", obj=task_ds) else: raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds) @@ -248,7 +248,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h # nested includes, and we want the include order printed correctly display.vv("statically imported: %s" % include_file) except AnsibleFileNotFound: - if action != 'include' or t.static or \ + if action not in C._ACTION_INCLUDE or t.static or \ C.DEFAULT_TASK_INCLUDES_STATIC or \ C.DEFAULT_HANDLER_INCLUDES_STATIC and use_handlers: raise @@ -286,7 +286,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h tags = tags.split(',') if len(tags) > 0: - if action in ('include_tasks', 'import_tasks'): + if action in C._ACTION_ALL_PROPER_INCLUDE_IMPORT_TASKS: raise AnsibleParserError('You cannot specify "tags" inline to the task, it is a task keyword') if len(ti_copy.tags) > 0: raise AnsibleParserError( @@ -316,7 +316,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h t.is_static = False task_list.append(t) - elif action in ('include_role', 'import_role'): + elif action in C._ACTION_ALL_PROPER_INCLUDE_IMPORT_ROLES: ir = IncludeRole.load( task_ds, block=block, @@ -329,7 +329,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h # 1. the user has set the 'static' option to false or true # 2. one of the appropriate config options was set is_static = False - if action == 'import_role': + if action in C._ACTION_IMPORT_ROLE: is_static = True elif ir.static is not None: @@ -340,7 +340,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h if is_static: if ir.loop is not None: - if action == 'import_role': + if action in C._ACTION_IMPORT_ROLE: raise AnsibleParserError("You cannot use loops on 'import_role' statements. You should use 'include_role' instead.", obj=task_ds) else: raise AnsibleParserError("You cannot use 'static' on an include_role with a loop", obj=task_ds) diff --git a/lib/ansible/playbook/included_file.py b/lib/ansible/playbook/included_file.py index 14663673368..2d209deb600 100644 --- a/lib/ansible/playbook/included_file.py +++ b/lib/ansible/playbook/included_file.py @@ -21,6 +21,7 @@ __metaclass__ = type import os +from ansible import constants as C from ansible.errors import AnsibleError from ansible.module_utils._text import to_text from ansible.playbook.task_include import TaskInclude @@ -67,7 +68,7 @@ class IncludedFile: original_host = res._host original_task = res._task - if original_task.action in ('include', 'include_tasks', 'include_role'): + if original_task.action in C._ACTION_ALL_INCLUDES: if original_task.loop: if 'results' not in res._result: continue @@ -111,7 +112,7 @@ class IncludedFile: templar = Templar(loader=loader, variables=task_vars) - if original_task.action in ('include', 'include_tasks'): + if original_task.action in C._ACTION_ALL_INCLUDE_TASKS: include_file = None if original_task: if original_task.static: diff --git a/lib/ansible/playbook/playbook_include.py b/lib/ansible/playbook/playbook_include.py index 04486ec7e02..e38f2cfa77f 100644 --- a/lib/ansible/playbook/playbook_include.py +++ b/lib/ansible/playbook/playbook_include.py @@ -21,6 +21,7 @@ __metaclass__ = type import os +import ansible.constants as C from ansible.errors import AnsibleParserError, AnsibleAssertionError from ansible.module_utils._text import to_bytes from ansible.module_utils.six import iteritems, string_types @@ -139,7 +140,7 @@ class PlaybookInclude(Base, Conditional, Taggable): new_ds.ansible_pos = ds.ansible_pos for (k, v) in iteritems(ds): - if k in ('include', 'import_playbook'): + if k in C._ACTION_ALL_IMPORT_PLAYBOOKS: self._preprocess_import(ds, new_ds, k, v) else: # some basic error checking, to make sure vars are properly diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py index 4e7530d7b4c..2ae80ca6f43 100644 --- a/lib/ansible/playbook/role_include.py +++ b/lib/ansible/playbook/role_include.py @@ -20,6 +20,7 @@ __metaclass__ = type from os.path import basename +import ansible.constants as C from ansible.errors import AnsibleParserError from ansible.playbook.attribute import FieldAttribute from ansible.playbook.block import Block @@ -127,7 +128,7 @@ class IncludeRole(TaskInclude): if ir._role_name is None: raise AnsibleParserError("'name' is a required field for %s." % ir.action, obj=data) - if 'public' in ir.args and ir.action != 'include_role': + if 'public' in ir.args and ir.action not in C._ACTION_INCLUDE_ROLE: raise AnsibleParserError('Invalid options for %s: public' % ir.action, obj=data) # validate bad args, otherwise we silently ignore @@ -144,7 +145,7 @@ class IncludeRole(TaskInclude): ir._from_files[from_key] = basename(args_value) apply_attrs = ir.args.get('apply', {}) - if apply_attrs and ir.action != 'include_role': + if apply_attrs and ir.action not in C._ACTION_INCLUDE_ROLE: raise AnsibleParserError('Invalid options for %s: apply' % ir.action, obj=data) elif not isinstance(apply_attrs, dict): raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data) diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 332bc5fcfce..f488d59e093 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -153,7 +153,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch): def __repr__(self): ''' returns a human readable representation of the task ''' - if self.get_name() == 'meta': + if self.get_name() in C._ACTION_META: return "TASK: meta (%s)" % self.args['_raw_params'] else: return "TASK: %s" % self.get_name() @@ -231,7 +231,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch): # the command/shell/script modules used to support the `cmd` arg, # which corresponds to what we now call _raw_params, so move that # value over to _raw_params (assuming it is empty) - if action in ('command', 'shell', 'script'): + if action in C._ACTION_HAS_CMD: if 'cmd' in args: if args.get('_raw_params', '') != '': raise AnsibleError("The 'cmd' argument cannot be used when other raw parameters are specified." @@ -263,7 +263,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch): # pre-2.0 syntax allowed variables for include statements at the top level of the task, # so we move those into the 'vars' dictionary here, and show a deprecation message # as we will remove this at some point in the future. - if action in ('include',) and k not in self._valid_attrs and k not in self.DEPRECATED_ATTRIBUTES: + if action in C._ACTION_INCLUDE and k not in self._valid_attrs and k not in self.DEPRECATED_ATTRIBUTES: display.deprecated("Specifying include variables at the top-level of the task is deprecated." " Please see:\nhttps://docs.ansible.com/ansible/playbooks_roles.html#task-include-files-and-encouraging-reuse\n\n" " for currently supported syntax regarding included files and variables", @@ -327,7 +327,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch): env[k] = templar.template(v, convert_bare=False) except AnsibleUndefinedVariable as e: error = to_native(e) - if self.action in ('setup', 'gather_facts') and 'ansible_facts.env' in error or 'ansible_env' in error: + if self.action in C._ACTION_FACT_GATHERING and 'ansible_facts.env' in error or 'ansible_env' in error: # ignore as fact gathering is required for 'env' facts return raise @@ -394,7 +394,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch): all_vars = dict() if self._parent: all_vars.update(self._parent.get_include_params()) - if self.action in ('include', 'include_tasks', 'include_role'): + if self.action in C._ACTION_ALL_INCLUDES: all_vars.update(self.vars) return all_vars diff --git a/lib/ansible/playbook/task_include.py b/lib/ansible/playbook/task_include.py index e3566046142..48eee18803a 100644 --- a/lib/ansible/playbook/task_include.py +++ b/lib/ansible/playbook/task_include.py @@ -76,7 +76,7 @@ class TaskInclude(Task): # validate bad args, otherwise we silently ignore bad_opts = my_arg_names.difference(self.VALID_ARGS) - if bad_opts and task.action in ('include_tasks', 'import_tasks'): + if bad_opts and task.action in C._ACTION_ALL_PROPER_INCLUDE_IMPORT_TASKS: raise AnsibleParserError('Invalid options for %s: %s' % (task.action, ','.join(list(bad_opts))), obj=data) if not task.args.get('_raw_params'): @@ -85,7 +85,7 @@ class TaskInclude(Task): raise AnsibleParserError('No file specified for %s' % task.action) apply_attrs = task.args.get('apply', {}) - if apply_attrs and task.action != 'include_tasks': + if apply_attrs and task.action not in C._ACTION_INCLUDE_TASKS: raise AnsibleParserError('Invalid options for %s: apply' % task.action, obj=data) elif not isinstance(apply_attrs, dict): raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data) @@ -98,7 +98,7 @@ class TaskInclude(Task): diff = set(ds.keys()).difference(self.VALID_INCLUDE_KEYWORDS) for k in diff: # This check doesn't handle ``include`` as we have no idea at this point if it is static or not - if ds[k] is not Sentinel and ds['action'] in ('include_tasks', 'include_role'): + if ds[k] is not Sentinel and ds['action'] in C._ACTION_ALL_INCLUDE_ROLE_TASKS: if C.INVALID_TASK_ATTRIBUTE_FAILED: raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (k, self.__class__.__name__), obj=ds) else: @@ -117,7 +117,7 @@ class TaskInclude(Task): we need to include the args of the include into the vars as they are params to the included tasks. But ONLY for 'include' ''' - if self.action != 'include': + if self.action not in C._ACTION_INCLUDE: all_vars = super(TaskInclude, self).get_vars() else: all_vars = dict() diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py index ce88c282c24..317494ad6c5 100644 --- a/lib/ansible/plugins/callback/__init__.py +++ b/lib/ansible/plugins/callback/__init__.py @@ -248,7 +248,7 @@ class CallbackBase(AnsiblePlugin): ''' removes data from results for display ''' # mostly controls that debug only outputs what it was meant to - if task_name == 'debug': + if task_name in C._ACTION_DEBUG: if 'msg' in result: # msg should be alone for key in list(result.keys()): diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 9f2bcae5ea6..6f35e2f8b6d 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -675,7 +675,7 @@ class StrategyBase: host_list = self.get_task_hosts(iterator, original_host, original_task) - if original_task.action == 'include_vars': + if original_task.action in C._ACTION_INCLUDE_VARS: for (var_name, var_value) in iteritems(result_item['ansible_facts']): # find the host we're actually referring too here, which may # be a host that is not really in inventory at all @@ -689,9 +689,10 @@ class StrategyBase: # we set BOTH fact and nonpersistent_facts (aka hostvar) # when fact is retrieved from cache in subsequent operations it will have the lower precedence, # but for playbook setting it the 'higher' precedence is kept - if original_task.action != 'set_fact' or cacheable: + is_set_fact = original_task.action in C._ACTION_SET_FACT + if not is_set_fact or cacheable: self._variable_manager.set_host_facts(target_host, result_item['ansible_facts'].copy()) - if original_task.action == 'set_fact': + if is_set_fact: self._variable_manager.set_nonpersistent_facts(target_host, result_item['ansible_facts'].copy()) if 'ansible_stats' in result_item and 'data' in result_item['ansible_stats'] and result_item['ansible_stats']['data']: diff --git a/lib/ansible/plugins/strategy/free.py b/lib/ansible/plugins/strategy/free.py index e832f22adb2..43fe9ef4c18 100644 --- a/lib/ansible/plugins/strategy/free.py +++ b/lib/ansible/plugins/strategy/free.py @@ -189,7 +189,7 @@ class StrategyModule(StrategyBase): del self._blocked_hosts[host_name] continue - if task.action == 'meta': + if task.action in C._ACTION_META: self._execute_meta(task, play_context, iterator, target_host=host) self._blocked_hosts[host_name] = False else: diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index 2fb524953c6..97373648693 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -31,6 +31,7 @@ DOCUMENTATION = ''' author: Ansible Core Team ''' +from ansible import constants as C from ansible.errors import AnsibleError, AnsibleAssertionError from ansible.executor.play_iterator import PlayIterator from ansible.module_utils.six import iteritems @@ -271,7 +272,7 @@ class StrategyModule(StrategyBase): # corresponding action plugin action = None - if task.action == 'meta': + if task.action in C._ACTION_META: # for the linear strategy, we run meta tasks just once and for # all hosts currently being iterated over rather than one host results.extend(self._execute_meta(task, play_context, iterator, host)) @@ -409,7 +410,7 @@ class StrategyModule(StrategyBase): 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 == 'meta') and iterator.is_failed(res._host): + if (res.is_failed() or res._task.action in C._ACTION_META) and iterator.is_failed(res._host): failed_hosts.append(res._host.name) elif res.is_unreachable(): unreachable_hosts.append(res._host.name) diff --git a/lib/ansible/utils/fqcn.py b/lib/ansible/utils/fqcn.py new file mode 100644 index 00000000000..a492be1f180 --- /dev/null +++ b/lib/ansible/utils/fqcn.py @@ -0,0 +1,33 @@ +# (c) 2020, Felix Fontein +# +# 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 . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def add_internal_fqcns(names): + ''' + Given a sequence of action/module names, returns a list of these names + with the same names with the prefixes `ansible.builtin.` and + `ansible.legacy.` added for all names that are not already FQCNs. + ''' + result = [] + for name in names: + result.append(name) + if '.' not in name: + result.append('ansible.builtin.%s' % name) + result.append('ansible.legacy.%s' % name) + return result diff --git a/lib/ansible/vars/manager.py b/lib/ansible/vars/manager.py index 0c819d41050..8b2a151dde9 100644 --- a/lib/ansible/vars/manager.py +++ b/lib/ansible/vars/manager.py @@ -219,7 +219,7 @@ class VariableManager: # if we have a task in this context, and that task has a role, make # sure it sees its defaults above any other roles, as we previously # (v1) made sure each task had a copy of its roles default vars - if task._role is not None and (play or task.action == 'include_role'): + if task._role is not None and (play or task.action in C._ACTION_INCLUDE_ROLE): all_vars = _combine_and_track(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()), "role '%s' defaults" % task._role.name) diff --git a/test/integration/targets/debug/main_fqcn.yml b/test/integration/targets/debug/main_fqcn.yml new file mode 100644 index 00000000000..d6a00fc84ce --- /dev/null +++ b/test/integration/targets/debug/main_fqcn.yml @@ -0,0 +1,6 @@ +- hosts: localhost + gather_facts: no + tasks: + - name: test item being present in the output + ansible.builtin.debug: var=item + loop: [1, 2, 3] diff --git a/test/integration/targets/debug/runme.sh b/test/integration/targets/debug/runme.sh index 4ff4e299a7b..5ccb1bfda6e 100755 --- a/test/integration/targets/debug/runme.sh +++ b/test/integration/targets/debug/runme.sh @@ -9,3 +9,9 @@ for i in 1 2 3; do grep "ok: \[localhost\] => (item=$i)" out grep "\"item\": $i" out done + +ansible-playbook main_fqcn.yml -i ../../inventory | tee out +for i in 1 2 3; do + grep "ok: \[localhost\] => (item=$i)" out + grep "\"item\": $i" out +done diff --git a/test/integration/targets/include_import/runme.sh b/test/integration/targets/include_import/runme.sh index 4ffee3ba421..e00abdb24c9 100755 --- a/test/integration/targets/include_import/runme.sh +++ b/test/integration/targets/include_import/runme.sh @@ -49,23 +49,29 @@ test "$(grep -E -c 'Expected a string for vars_from but got' test_include_role_v ## Max Recursion Depth # https://github.com/ansible/ansible/issues/23609 ANSIBLE_STRATEGY='linear' ansible-playbook test_role_recursion.yml -i inventory "$@" +ANSIBLE_STRATEGY='linear' ansible-playbook test_role_recursion_fqcn.yml -i inventory "$@" ## Nested tasks # https://github.com/ansible/ansible/issues/34782 ANSIBLE_STRATEGY='linear' ansible-playbook test_nested_tasks.yml -i inventory "$@" +ANSIBLE_STRATEGY='linear' ansible-playbook test_nested_tasks_fqcn.yml -i inventory "$@" ANSIBLE_STRATEGY='free' ansible-playbook test_nested_tasks.yml -i inventory "$@" +ANSIBLE_STRATEGY='free' ansible-playbook test_nested_tasks_fqcn.yml -i inventory "$@" ## Tons of top level include_tasks # https://github.com/ansible/ansible/issues/36053 # Fixed by https://github.com/ansible/ansible/pull/36075 gen_task_files ANSIBLE_STRATEGY='linear' ansible-playbook test_copious_include_tasks.yml -i inventory "$@" +ANSIBLE_STRATEGY='linear' ansible-playbook test_copious_include_tasks_fqcn.yml -i inventory "$@" ANSIBLE_STRATEGY='free' ansible-playbook test_copious_include_tasks.yml -i inventory "$@" +ANSIBLE_STRATEGY='free' ansible-playbook test_copious_include_tasks_fqcn.yml -i inventory "$@" rm -f tasks/hello/*.yml # Inlcuded tasks should inherit attrs from non-dynamic blocks in parent chain # https://github.com/ansible/ansible/pull/38827 ANSIBLE_STRATEGY='linear' ansible-playbook test_grandparent_inheritance.yml -i inventory "$@" +ANSIBLE_STRATEGY='linear' ansible-playbook test_grandparent_inheritance_fqcn.yml -i inventory "$@" # undefined_var ANSIBLE_STRATEGY='linear' ansible-playbook undefined_var/playbook.yml -i inventory "$@" @@ -119,3 +125,4 @@ test "$(grep -c 'ok=3' test_allow_single_role_dup.out)" = 1 ANSIBLE_HOST_PATTERN_MISMATCH=error ansible-playbook empty_group_warning/playbook.yml ansible-playbook test_include_loop.yml "$@" +ansible-playbook test_include_loop_fqcn.yml "$@" diff --git a/test/integration/targets/include_import/test_copious_include_tasks_fqcn.yml b/test/integration/targets/include_import/test_copious_include_tasks_fqcn.yml new file mode 100644 index 00000000000..32fa9abc6c5 --- /dev/null +++ b/test/integration/targets/include_import/test_copious_include_tasks_fqcn.yml @@ -0,0 +1,44 @@ +- name: Test many ansible.builtin.include_tasks + hosts: testhost + gather_facts: no + + tasks: + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-001.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-002.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-003.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-004.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-005.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-006.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-007.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-008.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-009.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-010.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-011.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-012.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-013.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-014.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-015.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-016.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-017.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-018.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-019.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-020.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-021.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-022.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-023.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-024.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-025.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-026.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-027.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-028.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-029.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-030.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-031.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-032.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-033.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-034.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-035.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-036.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-037.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-038.yml" + - ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/hello/tasks-file-039.yml" diff --git a/test/integration/targets/include_import/test_grandparent_inheritance_fqcn.yml b/test/integration/targets/include_import/test_grandparent_inheritance_fqcn.yml new file mode 100644 index 00000000000..37a0ad0d6a6 --- /dev/null +++ b/test/integration/targets/include_import/test_grandparent_inheritance_fqcn.yml @@ -0,0 +1,29 @@ +--- +- hosts: testhost + gather_facts: false + tasks: + - debug: + var: inventory_hostname + + - name: Test included tasks inherit from block + check_mode: true + block: + - ansible.builtin.include_tasks: grandchild/block_include_tasks.yml + + - debug: + var: block_include_result + + - assert: + that: + - block_include_result is skipped + + - name: Test included tasks inherit deeply from import + ansible.builtin.import_tasks: grandchild/import.yml + check_mode: true + + - debug: + var: import_include_include_result + + - assert: + that: + - import_include_include_result is skipped diff --git a/test/integration/targets/include_import/test_include_loop_fqcn.yml b/test/integration/targets/include_import/test_include_loop_fqcn.yml new file mode 100644 index 00000000000..62d91f227d5 --- /dev/null +++ b/test/integration/targets/include_import/test_include_loop_fqcn.yml @@ -0,0 +1,17 @@ +- hosts: localhost + gather_facts: false + tasks: + - name: skipped include undefined loop + ansible.builtin.include_tasks: doesnt_matter.yml + loop: '{{ lkjsdflkjsdlfkjsdlfkjsdf }}' + when: false + register: skipped_include + + - debug: + var: skipped_include + + - assert: + that: + - skipped_include.results is undefined + - skipped_include.skip_reason is defined + - skipped_include is skipped diff --git a/test/integration/targets/include_import/test_nested_tasks_fqcn.yml b/test/integration/targets/include_import/test_nested_tasks_fqcn.yml new file mode 100644 index 00000000000..14e72eed1bf --- /dev/null +++ b/test/integration/targets/include_import/test_nested_tasks_fqcn.yml @@ -0,0 +1,6 @@ +- name: >- + verify that multiple level of nested statements and + include+meta doesnt mess included files mecanisms + hosts: testhost + tasks: + - ansible.builtin.include_tasks: ./tasks/nested/nested.yml diff --git a/test/integration/targets/include_import/test_role_recursion_fqcn.yml b/test/integration/targets/include_import/test_role_recursion_fqcn.yml new file mode 100644 index 00000000000..13d8d2cbd26 --- /dev/null +++ b/test/integration/targets/include_import/test_role_recursion_fqcn.yml @@ -0,0 +1,7 @@ +- name: Test max recursion depth + hosts: testhost + + tasks: + - ansible.builtin.import_role: + name: role1 + tasks_from: r1t01.yml diff --git a/test/integration/targets/include_vars/tasks/main.yml b/test/integration/targets/include_vars/tasks/main.yml index ab2442c6fa2..799d7b26a66 100644 --- a/test/integration/targets/include_vars/tasks/main.yml +++ b/test/integration/targets/include_vars/tasks/main.yml @@ -65,12 +65,12 @@ - "testing == 456" - "base_dir == 'services'" - "webapp_containers == 10" - - "{{ include_every_dir.ansible_included_var_files | length }} == 6" + - "{{ include_every_dir.ansible_included_var_files | length }} == 7" - "'vars/all/all.yml' in include_every_dir.ansible_included_var_files[0]" - "'vars/environments/development/all.yml' in include_every_dir.ansible_included_var_files[1]" - "'vars/environments/development/services/webapp.yml' in include_every_dir.ansible_included_var_files[2]" - - "'vars/services/webapp.yml' in include_every_dir.ansible_included_var_files[4]" - - "'vars/webapp/file_without_extension' in include_every_dir.ansible_included_var_files[5]" + - "'vars/services/webapp.yml' in include_every_dir.ansible_included_var_files[5]" + - "'vars/webapp/file_without_extension' in include_every_dir.ansible_included_var_files[6]" - name: include every directory in vars except files matching webapp.yml include_vars: @@ -85,7 +85,7 @@ that: - "testing == 789" - "base_dir == 'environments/development'" - - "{{ include_without_webapp.ansible_included_var_files | length }} == 3" + - "{{ include_without_webapp.ansible_included_var_files | length }} == 4" - "'webapp.yml' not in '{{ include_without_webapp.ansible_included_var_files | join(' ') }}'" - "'file_without_extension' not in '{{ include_without_webapp.ansible_included_var_files | join(' ') }}'" @@ -152,3 +152,13 @@ assert: that: - "'Could not find file' in include_with_non_existent_file.message" + +- name: include var (FQCN) with raw params + ansible.builtin.include_vars: > + services/service_vars_fqcn.yml + +- name: Verify that FQCN of include_vars works + assert: + that: + - "'my_custom_service' == service_name_fqcn" + - "'my_custom_service' == service_name_tmpl_fqcn" diff --git a/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml new file mode 100644 index 00000000000..2c04fee505a --- /dev/null +++ b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml @@ -0,0 +1,3 @@ +--- +service_name_fqcn: 'my_custom_service' +service_name_tmpl_fqcn: '{{ service_name_fqcn }}' \ No newline at end of file diff --git a/test/integration/targets/includes/roles/test_includes_free/tasks/inner_fqcn.yml b/test/integration/targets/includes/roles/test_includes_free/tasks/inner_fqcn.yml new file mode 100644 index 00000000000..5b4ce04041c --- /dev/null +++ b/test/integration/targets/includes/roles/test_includes_free/tasks/inner_fqcn.yml @@ -0,0 +1,2 @@ +- set_fact: + inner_fqcn: "reached" diff --git a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml index 7bc19faae1f..5ae7882f4cd 100644 --- a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml +++ b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml @@ -4,3 +4,6 @@ - include: inner.yml with_items: - '1' +- ansible.builtin.include: inner_fqcn.yml + with_items: + - '1' diff --git a/test/integration/targets/includes/test_include_free.yml b/test/integration/targets/includes/test_include_free.yml index 4cdc1bca50a..dedad734852 100644 --- a/test/integration/targets/includes/test_include_free.yml +++ b/test/integration/targets/includes/test_include_free.yml @@ -7,3 +7,4 @@ - assert: that: - "inner == 'reached'" + - "inner_fqcn == 'reached'" diff --git a/test/integration/targets/meta_tasks/runme.sh b/test/integration/targets/meta_tasks/runme.sh index b8defed26d9..3ee419cb069 100755 --- a/test/integration/targets/meta_tasks/runme.sh +++ b/test/integration/targets/meta_tasks/runme.sh @@ -11,6 +11,14 @@ for test_strategy in linear free; do grep -q '"skip_reason": "end_host conditional evaluated to False, continuing execution for testhost"' <<< "$out" grep -q "play not ended for testhost" <<< "$out" grep -qv "play not ended for testhost2" <<< "$out" + + out="$(ansible-playbook test_end_host_fqcn.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")" + + grep -q "META: end_host conditional evaluated to false, continuing execution for testhost" <<< "$out" + grep -q "META: ending play for testhost2" <<< "$out" + grep -q '"skip_reason": "end_host conditional evaluated to False, continuing execution for testhost"' <<< "$out" + grep -q "play not ended for testhost" <<< "$out" + grep -qv "play not ended for testhost2" <<< "$out" done # test end_host meta task, on all hosts @@ -21,6 +29,13 @@ for test_strategy in linear free; do grep -q "META: ending play for testhost2" <<< "$out" grep -qv "play not ended for testhost" <<< "$out" grep -qv "play not ended for testhost2" <<< "$out" + + out="$(ansible-playbook test_end_host_all_fqcn.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")" + + grep -q "META: ending play for testhost" <<< "$out" + grep -q "META: ending play for testhost2" <<< "$out" + grep -qv "play not ended for testhost" <<< "$out" + grep -qv "play not ended for testhost2" <<< "$out" done # test end_play meta task @@ -29,4 +44,9 @@ for test_strategy in linear free; do grep -q "META: ending play" <<< "$out" grep -qv 'Failed to end using end_play' <<< "$out" + + out="$(ansible-playbook test_end_play_fqcn.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")" + + grep -q "META: ending play" <<< "$out" + grep -qv 'Failed to end using end_play' <<< "$out" done diff --git a/test/integration/targets/meta_tasks/test_end_host_all_fqcn.yml b/test/integration/targets/meta_tasks/test_end_host_all_fqcn.yml new file mode 100644 index 00000000000..78b5a2e9c88 --- /dev/null +++ b/test/integration/targets/meta_tasks/test_end_host_all_fqcn.yml @@ -0,0 +1,13 @@ +- name: "Testing end_host all hosts with strategy={{ test_strategy | default('linear') }}" + hosts: + - testhost + - testhost2 + gather_facts: no + strategy: "{{ test_strategy | default('linear') }}" + tasks: + - debug: + + - ansible.builtin.meta: end_host + + - debug: + msg: "play not ended {{ inventory_hostname }}" diff --git a/test/integration/targets/meta_tasks/test_end_host_fqcn.yml b/test/integration/targets/meta_tasks/test_end_host_fqcn.yml new file mode 100644 index 00000000000..bdb38b536e7 --- /dev/null +++ b/test/integration/targets/meta_tasks/test_end_host_fqcn.yml @@ -0,0 +1,14 @@ +- name: "Testing end_host with strategy={{ test_strategy | default('linear') }}" + hosts: + - testhost + - testhost2 + gather_facts: no + strategy: "{{ test_strategy | default('linear') }}" + tasks: + - debug: + + - ansible.builtin.meta: end_host + when: "host_var_role_name == 'role2'" # end play for testhost2, see inventory + + - debug: + msg: "play not ended for {{ inventory_hostname }}" diff --git a/test/integration/targets/meta_tasks/test_end_play_fqcn.yml b/test/integration/targets/meta_tasks/test_end_play_fqcn.yml new file mode 100644 index 00000000000..2ae67fbea20 --- /dev/null +++ b/test/integration/targets/meta_tasks/test_end_play_fqcn.yml @@ -0,0 +1,12 @@ +- name: Testing end_play with strategy {{ test_strategy | default('linear') }} + hosts: testhost:testhost2 + gather_facts: no + strategy: "{{ test_strategy | default('linear') }}" + tasks: + - debug: + msg: "Testing end_play on host {{ inventory_hostname }}" + + - ansible.builtin.meta: end_play + + - fail: + msg: 'Failed to end using end_play' diff --git a/test/integration/targets/set_fact/incremental.yml b/test/integration/targets/set_fact/incremental.yml index 03da5bb6c87..3f7aa6c492c 100644 --- a/test/integration/targets/set_fact/incremental.yml +++ b/test/integration/targets/set_fact/incremental.yml @@ -17,3 +17,19 @@ assert: that: - dig_list == ['one', 'two', 'three', 'four'] + + - name: Generate inline loop for set_fact (FQCN) + ansible.builtin.set_fact: + dig_list_fqcn: "{{ dig_list_fqcn + [ item ] }}" + loop: + - two + - three + - four + vars: + dig_list_fqcn: + - one + + - name: verify cumulative set fact worked (FQCN) + assert: + that: + - dig_list_fqcn == ['one', 'two', 'three', 'four'] diff --git a/test/integration/targets/set_fact/nowarn_clean_facts.yml b/test/integration/targets/set_fact/nowarn_clean_facts.yml index 9cde7c64979..74f908d0479 100644 --- a/test/integration/targets/set_fact/nowarn_clean_facts.yml +++ b/test/integration/targets/set_fact/nowarn_clean_facts.yml @@ -5,3 +5,6 @@ - name: set ssh jump host args set_fact: ansible_ssh_common_args: "-o ProxyCommand='ssh -W %h:%p -q root@localhost'" + - name: set ssh jump host args (FQCN) + ansible.builtin.set_fact: + ansible_ssh_common_args: "-o ProxyCommand='ssh -W %h:%p -q root@localhost'" diff --git a/test/integration/targets/set_fact/set_fact_bool_conv.yml b/test/integration/targets/set_fact/set_fact_bool_conv.yml index 2e06d75c87f..8df249bee21 100644 --- a/test/integration/targets/set_fact/set_fact_bool_conv.yml +++ b/test/integration/targets/set_fact/set_fact_bool_conv.yml @@ -18,3 +18,18 @@ - this_is_also_string == False - this_is_another_string == False - this_is_more_strings == False + + - ansible.builtin.set_fact: + this_is_string_fqcn: "yes" + this_is_not_string_fqcn: yes + this_is_also_string_fqcn: "{{ string_var }}" + this_is_another_string_fqcn: !!str "{% set thing = '' + string_var + '' %}{{ thing }}" + this_is_more_strings_fqcn: '{{ string_var + "" }}' + + - assert: + that: + - this_is_string_fqcn == True + - this_is_not_string_fqcn == True + - this_is_also_string_fqcn == False + - this_is_another_string_fqcn == False + - this_is_more_strings_fqcn == False diff --git a/test/integration/targets/set_fact/set_fact_bool_conv_jinja2_native.yml b/test/integration/targets/set_fact/set_fact_bool_conv_jinja2_native.yml index 4721f4374f7..2642599f489 100644 --- a/test/integration/targets/set_fact/set_fact_bool_conv_jinja2_native.yml +++ b/test/integration/targets/set_fact/set_fact_bool_conv_jinja2_native.yml @@ -18,3 +18,18 @@ - this_is_also_string == 'no' - this_is_another_string == 'no' - this_is_more_strings == 'no' + + - ansible.builtin.set_fact: + this_is_string_fqcn: "yes" + this_is_not_string_fqcn: yes + this_is_also_string_fqcn: "{{ string_var }}" + this_is_another_string_fqcn: !!str "{% set thing = '' + string_var + '' %}{{ thing }}" + this_is_more_strings_fqcn: '{{ string_var + "" }}' + + - assert: + that: + - this_is_string_fqcn == 'yes' + - this_is_not_string_fqcn == True + - this_is_also_string_fqcn == 'no' + - this_is_another_string_fqcn == 'no' + - this_is_more_strings_fqcn == 'no' diff --git a/test/integration/targets/set_fact/set_fact_cached_1.yml b/test/integration/targets/set_fact/set_fact_cached_1.yml index 450f8da28ea..01c9f1e021c 100644 --- a/test/integration/targets/set_fact/set_fact_cached_1.yml +++ b/test/integration/targets/set_fact/set_fact_cached_1.yml @@ -45,6 +45,49 @@ that: - fact_not_cached == 'this_should_not_be_cached!' + - name: show foobar fact before (FQCN) + debug: + var: ansible_foobar_fqcn + + - name: set a persistent fact foobar (FQCN) + set_fact: + ansible_foobar_fqcn: 'foobar_fqcn_from_set_fact_cacheable' + cacheable: true + + - name: show foobar fact after (FQCN) + debug: + var: ansible_foobar_fqcn + + - name: assert ansible_foobar_fqcn is correct value (FQCN) + assert: + that: + - ansible_foobar_fqcn == 'foobar_fqcn_from_set_fact_cacheable' + + - name: set a non persistent fact that will not be cached (FQCN) + set_fact: + ansible_foobar_not_cached_fqcn: 'this_should_not_be_cached' + + - name: show ansible_foobar_not_cached_fqcn fact after being set (FQCN) + debug: + var: ansible_foobar_not_cached_fqcn + + - name: assert ansible_foobar_not_cached_fqcn is correct value (FQCN) + assert: + that: + - ansible_foobar_not_cached_fqcn == 'this_should_not_be_cached' + + - name: set another non persistent fact that will not be cached (FQCN) + set_fact: "cacheable=no fact_not_cached_fqcn='this_should_not_be_cached!'" + + - name: show fact_not_cached_fqcn fact after being set (FQCN) + debug: + var: fact_not_cached_fqcn + + - name: assert fact_not_cached_fqcn is correct value (FQCN) + assert: + that: + - fact_not_cached_fqcn == 'this_should_not_be_cached!' + - name: the second play hosts: localhost tasks: @@ -57,26 +100,43 @@ that: - ansible_foobar == 'foobar_from_set_fact_cacheable' -- name: show ansible_nodename + - name: show foobar fact after second play (FQCN) + debug: + var: ansible_foobar_fqcn + + - name: assert ansible_foobar is correct value (FQCN) + assert: + that: + - ansible_foobar_fqcn == 'foobar_fqcn_from_set_fact_cacheable' + +- name: show ansible_nodename and ansible_os_family hosts: localhost tasks: - name: show nodename fact after second play debug: var: ansible_nodename + - name: show os_family fact after second play (FQCN) + debug: + var: ansible_os_family -- name: show ansible_nodename overridden with var +- name: show ansible_nodename and ansible_os_family overridden with var hosts: localhost vars: ansible_nodename: 'nodename_from_play_vars' + ansible_os_family: 'os_family_from_play_vars' tasks: - name: show nodename fact after second play debug: var: ansible_nodename + - name: show os_family fact after second play (FQCN) + debug: + var: ansible_os_family - name: verify ansible_nodename from vars overrides the fact hosts: localhost vars: ansible_nodename: 'nodename_from_play_vars' + ansible_os_family: 'os_family_from_play_vars' tasks: - name: show nodename fact debug: @@ -87,7 +147,16 @@ that: - ansible_nodename == 'nodename_from_play_vars' -- name: set_fact ansible_nodename + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_play_vars' + +- name: set_fact ansible_nodename and ansible_os_family hosts: localhost tasks: - name: set a persistent fact nodename @@ -103,10 +172,24 @@ that: - ansible_nodename == 'nodename_from_set_fact_cacheable' -- name: verify that set_fact ansible_nodename non_cacheable overrides ansible_nodename in vars + - name: set a persistent fact os_family (FQCN) + ansible.builtin.set_fact: + ansible_os_family: 'os_family_from_set_fact_cacheable' + + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_set_fact_cacheable' + +- name: verify that set_fact ansible_xxx non_cacheable overrides ansible_xxx in vars hosts: localhost vars: ansible_nodename: 'nodename_from_play_vars' + ansible_os_family: 'os_family_from_play_vars' tasks: - name: show nodename fact debug: @@ -117,10 +200,20 @@ that: - ansible_nodename == 'nodename_from_set_fact_cacheable' -- name: verify that set_fact_cacheable in previous play overrides ansible_nodename in vars + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_set_fact_cacheable' + +- name: verify that set_fact_cacheable in previous play overrides ansible_xxx in vars hosts: localhost vars: ansible_nodename: 'nodename_from_play_vars' + ansible_os_family: 'os_family_from_play_vars' tasks: - name: show nodename fact debug: @@ -131,7 +224,16 @@ that: - ansible_nodename == 'nodename_from_set_fact_cacheable' -- name: set_fact ansible_nodename cacheable + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_set_fact_cacheable' + +- name: set_fact ansible_nodename and ansible_os_family cacheable hosts: localhost tasks: - name: set a persistent fact nodename @@ -148,11 +250,26 @@ that: - ansible_nodename == 'nodename_from_set_fact_cacheable' + - name: set a persistent fact os_family (FQCN) + ansible.builtin.set_fact: + ansible_os_family: 'os_family_from_set_fact_cacheable' + cacheable: true -- name: verify that set_fact_cacheable in previous play overrides ansible_nodename in vars + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_set_fact_cacheable' + + +- name: verify that set_fact_cacheable in previous play overrides ansible_xxx in vars hosts: localhost vars: ansible_nodename: 'nodename_from_play_vars' + ansible_os_family: 'os_family_from_play_vars' tasks: - name: show nodename fact debug: @@ -163,10 +280,20 @@ that: - ansible_nodename == 'nodename_from_set_fact_cacheable' + - name: show os_family fact (FQCN) + debug: + var: ansible_os_family + + - name: assert ansible_os_family is correct value (FQCN) + assert: + that: + - ansible_os_family == 'os_family_from_set_fact_cacheable' + - name: the fourth play hosts: localhost vars: ansible_foobar: 'foobar_from_play_vars' + ansible_foobar_fqcn: 'foobar_fqcn_from_play_vars' tasks: - name: show example fact debug: @@ -181,3 +308,17 @@ assert: that: - ansible_example == 'foobar_from_set_fact_cacheable' + + - name: show example fact (FQCN) + debug: + var: ansible_example_fqcn + + - name: set a persistent fact example (FQCN) + set_fact: + ansible_example_fqcn: 'foobar_fqcn_from_set_fact_cacheable' + cacheable: true + + - name: assert ansible_example_fqcn is correct value (FQCN) + assert: + that: + - ansible_example_fqcn == 'foobar_fqcn_from_set_fact_cacheable' diff --git a/test/integration/targets/set_fact/set_fact_cached_2.yml b/test/integration/targets/set_fact/set_fact_cached_2.yml index 7ca26b9dcb0..7df9224403a 100644 --- a/test/integration/targets/set_fact/set_fact_cached_2.yml +++ b/test/integration/targets/set_fact/set_fact_cached_2.yml @@ -28,3 +28,30 @@ assert: that: - fact_not_cached is undefined + + - name: show ansible_foobar_fqcn fact (FQCN) + debug: + var: ansible_foobar_fqcn + + - name: assert ansible_foobar_fqcn is correct value when read from cache (FQCN) + assert: + that: + - ansible_foobar_fqcn == 'foobar_fqcn_from_set_fact_cacheable' + + - name: show ansible_foobar_fqcn_not_cached fact (FQCN) + debug: + var: ansible_foobar_fqcn_not_cached + + - name: assert ansible_foobar_fqcn_not_cached is not cached (FQCN) + assert: + that: + - ansible_foobar_fqcn_not_cached is undefined + + - name: show fact_not_cached_fqcn fact (FQCN) + debug: + var: fact_not_cached_fqcn + + - name: assert fact_not_cached_fqcn is not cached (FQCN) + assert: + that: + - fact_not_cached_fqcn is undefined diff --git a/test/integration/targets/set_fact/set_fact_no_cache.yml b/test/integration/targets/set_fact/set_fact_no_cache.yml index 3dffb65f550..f5a99792f1b 100644 --- a/test/integration/targets/set_fact/set_fact_no_cache.yml +++ b/test/integration/targets/set_fact/set_fact_no_cache.yml @@ -19,3 +19,21 @@ assert: that: - ansible_foobar_not_cached is undefined + + - name: show ansible_foobar fact (FQCN) + debug: + var: ansible_foobar_fqcn + + - name: assert ansible_foobar is correct value (FQCN) + assert: + that: + - ansible_foobar_fqcn is undefined + + - name: show ansible_foobar_not_cached fact (FQCN) + debug: + var: ansible_foobar_fqcn_not_cached + + - name: assert ansible_foobar_not_cached is not cached (FQCN) + assert: + that: + - ansible_foobar_fqcn_not_cached is undefined