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.
This commit is contained in:
Felix Fontein 2020-11-03 14:51:31 +01:00 committed by GitHub
parent 79021df2e4
commit da60525610
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 571 additions and 64 deletions

View file

@ -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)."

View file

@ -71,7 +71,7 @@ class AdHocCLI(CLI):
'timeout': context.CLIARGS['task_timeout']} 'timeout': context.CLIARGS['task_timeout']}
# avoid adding to tasks that don't support it, unless set, then give user an error # 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['async_val'] = async_val
mytask['poll'] = poll mytask['poll'] = poll
@ -120,7 +120,7 @@ class AdHocCLI(CLI):
raise AnsibleOptionsError(err) raise AnsibleOptionsError(err)
# Avoid modules that don't work with ad-hoc # 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" raise AnsibleOptionsError("'%s' is not a valid action for ad-hoc commands"
% context.CLIARGS['module_name']) % context.CLIARGS['module_name'])

View file

@ -185,7 +185,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
result = None result = None
try: 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) task = dict(action=dict(module=module, args=parse_kv(module_args, check_raw=check_raw)), timeout=self.task_timeout)
play_ds = dict( play_ds = dict(
name="Ansible Shell", name="Ansible Shell",

View file

@ -8,6 +8,7 @@ __metaclass__ = type
import os import os
import stat import stat
from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
@ -170,7 +171,7 @@ class PlaybookCLI(CLI):
if isinstance(task, Block): if isinstance(task, Block):
taskmsg += _process_block(task) taskmsg += _process_block(task)
else: else:
if task.action == 'meta' and task.implicit: if task.action in C._ACTION_META and task.implicit:
continue continue
all_tags.update(task.tags) all_tags.update(task.tags)

View file

@ -17,6 +17,7 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.common.collections import Sequence from ansible.module_utils.common.collections import Sequence
from ansible.module_utils.parsing.convert_bool import boolean, BOOLEANS_TRUE from ansible.module_utils.parsing.convert_bool import boolean, BOOLEANS_TRUE
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.utils.fqcn import add_internal_fqcns
def _warning(msg): 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 IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search
INTERNAL_RESULT_KEYS = ('add_host', 'add_group') INTERNAL_RESULT_KEYS = ('add_host', 'add_group')
LOCALHOST = ('127.0.0.1', 'localhost', '::1') LOCALHOST = ('127.0.0.1', 'localhost', '::1')
MODULE_REQUIRE_ARGS = ('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', MODULE_REQUIRE_ARGS = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell',
'ansible.windows.win_shell', 'raw', 'script') 'ansible.windows.win_shell', 'raw', 'script')))
MODULE_NO_JSON = ('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell', MODULE_NO_JSON = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell',
'ansible.windows.win_shell', 'raw') 'ansible.windows.win_shell', 'raw')))
RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python', 'ansible_facts') RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python', 'ansible_facts')
TREE_DIR = None TREE_DIR = None
VAULT_VERSION_MIN = 1.0 VAULT_VERSION_MIN = 1.0
@ -164,3 +165,27 @@ for setting in config.data.get_settings():
for warn in config.WARNINGS: for warn in config.WARNINGS:
_warning(warn) _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

View file

@ -477,7 +477,7 @@ class TaskExecutor:
# if this task is a TaskInclude, we just return now with a success code so the # 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 # 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_args = self._task.args.copy()
include_file = include_args.pop('_raw_params', None) include_file = include_args.pop('_raw_params', None)
if not include_file: if not include_file:
@ -487,7 +487,7 @@ class TaskExecutor:
return dict(include=include_file, include_args=include_args) 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 # 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() include_args = self._task.args.copy()
return dict(include_args=include_args) return dict(include_args=include_args)
@ -624,7 +624,7 @@ class TaskExecutor:
return failed_when_result return failed_when_result
if 'ansible_facts' in 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']) vars_copy.update(result['ansible_facts'])
else: else:
# TODO: cleaning of facts should eventually become part of taskresults instead of vars # 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) variables[self._task.register] = result = wrap_var(result)
if 'ansible_facts' in 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']) variables.update(result['ansible_facts'])
else: else:
# TODO: cleaning of facts should eventually become part of taskresults instead of vars # TODO: cleaning of facts should eventually become part of taskresults instead of vars

View file

@ -113,7 +113,7 @@ class TaskResult:
result = TaskResult(self._host, self._task, {}, self._task_fields) result = TaskResult(self._host, self._task, {}, self._task_fields)
# statuses are already reflected on the event type # 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 # debug is verbose by default to display vars, no need to add invocation
ignore = _IGNORE + ('invocation',) ignore = _IGNORE + ('invocation',)
else: else:

View file

@ -26,13 +26,14 @@ from ansible.module_utils._text import to_text
from ansible.parsing.splitter import parse_kv, split_args from ansible.parsing.splitter import parse_kv, split_args
from ansible.plugins.loader import module_loader, action_loader from ansible.plugins.loader import module_loader, action_loader
from ansible.template import Templar from ansible.template import Templar
from ansible.utils.fqcn import add_internal_fqcns
from ansible.utils.sentinel import Sentinel from ansible.utils.sentinel import Sentinel
# For filtering out modules correctly below # For filtering out modules correctly below
FREEFORM_ACTIONS = frozenset(C.MODULE_REQUIRE_ARGS) FREEFORM_ACTIONS = frozenset(C.MODULE_REQUIRE_ARGS)
RAW_PARAM_MODULES = FREEFORM_ACTIONS.union(( RAW_PARAM_MODULES = FREEFORM_ACTIONS.union(add_internal_fqcns((
'include', 'include',
'include_vars', 'include_vars',
'include_tasks', 'include_tasks',
@ -43,16 +44,16 @@ RAW_PARAM_MODULES = FREEFORM_ACTIONS.union((
'group_by', 'group_by',
'set_fact', 'set_fact',
'meta', 'meta',
)) )))
BUILTIN_TASKS = frozenset(( BUILTIN_TASKS = frozenset(add_internal_fqcns((
'meta', 'meta',
'include', 'include',
'include_tasks', 'include_tasks',
'include_role', 'include_role',
'import_tasks', 'import_tasks',
'import_role' 'import_role'
)) )))
class ModuleArgsParser: class ModuleArgsParser:

View file

@ -91,15 +91,19 @@ class Playbook:
self._loader.set_basedir(cur_basedir) self._loader.set_basedir(cur_basedir)
raise AnsibleParserError("playbook entries must be either a valid play or an include statement", obj=entry) 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 any(action in entry for action in C._ACTION_ALL_IMPORT_PLAYBOOKS):
if 'include' in entry: if any(action in entry for action in C._ACTION_INCLUDE):
display.deprecated("'include' for playbook includes. You should use 'import_playbook' instead", display.deprecated("'include' for playbook includes. You should use 'import_playbook' instead",
version="2.12", collection_name='ansible.builtin') version="2.12", collection_name='ansible.builtin')
pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader) pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader)
if pb is not None: if pb is not None:
self._entries.extend(pb._entries) self._entries.extend(pb._entries)
else: 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) display.display("skipping playbook '%s' due to conditional test failure" % which, color=C.COLOR_SKIP)
else: else:
entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader, vars=vars) entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader, vars=vars)

View file

@ -19,6 +19,7 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import ansible.constants as C
from ansible.errors import AnsibleParserError from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
@ -374,8 +375,8 @@ class Block(Base, Conditional, CollectionSearch, Taggable):
filtered_block = evaluate_block(task) filtered_block = evaluate_block(task)
if filtered_block.has_tasks(): if filtered_block.has_tasks():
tmp_list.append(filtered_block) tmp_list.append(filtered_block)
elif ((task.action == 'meta' and task.implicit) or elif ((task.action in C._ACTION_META and task.implicit) or
(task.action == 'include' and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) 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)): task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)):
tmp_list.append(task) tmp_list.append(task)
return tmp_list return tmp_list

View file

@ -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 # 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) 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: if use_handlers:
include_class = HandlerTaskInclude 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: # check to see if this include is dynamic or static:
# 1. the user has set the 'static' option to false or true # 1. the user has set the 'static' option to false or true
# 2. one of the appropriate config options was set # 2. one of the appropriate config options was set
if action == 'include_tasks': if action in C._ACTION_INCLUDE_TASKS:
is_static = False is_static = False
elif action == 'import_tasks': elif action in C._ACTION_IMPORT_TASKS:
is_static = True is_static = True
elif t.static is not None: elif t.static is not None:
display.deprecated("The use of 'static' has been deprecated. " 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 is_static:
if t.loop is not None: 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) raise AnsibleParserError("You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.", obj=task_ds)
else: else:
raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds) 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 # nested includes, and we want the include order printed correctly
display.vv("statically imported: %s" % include_file) display.vv("statically imported: %s" % include_file)
except AnsibleFileNotFound: 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_TASK_INCLUDES_STATIC or \
C.DEFAULT_HANDLER_INCLUDES_STATIC and use_handlers: C.DEFAULT_HANDLER_INCLUDES_STATIC and use_handlers:
raise raise
@ -286,7 +286,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
tags = tags.split(',') tags = tags.split(',')
if len(tags) > 0: 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') raise AnsibleParserError('You cannot specify "tags" inline to the task, it is a task keyword')
if len(ti_copy.tags) > 0: if len(ti_copy.tags) > 0:
raise AnsibleParserError( 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 t.is_static = False
task_list.append(t) task_list.append(t)
elif action in ('include_role', 'import_role'): elif action in C._ACTION_ALL_PROPER_INCLUDE_IMPORT_ROLES:
ir = IncludeRole.load( ir = IncludeRole.load(
task_ds, task_ds,
block=block, 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 # 1. the user has set the 'static' option to false or true
# 2. one of the appropriate config options was set # 2. one of the appropriate config options was set
is_static = False is_static = False
if action == 'import_role': if action in C._ACTION_IMPORT_ROLE:
is_static = True is_static = True
elif ir.static is not None: 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 is_static:
if ir.loop is not None: 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) raise AnsibleParserError("You cannot use loops on 'import_role' statements. You should use 'include_role' instead.", obj=task_ds)
else: else:
raise AnsibleParserError("You cannot use 'static' on an include_role with a loop", obj=task_ds) raise AnsibleParserError("You cannot use 'static' on an include_role with a loop", obj=task_ds)

View file

@ -21,6 +21,7 @@ __metaclass__ = type
import os import os
from ansible import constants as C
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.playbook.task_include import TaskInclude from ansible.playbook.task_include import TaskInclude
@ -67,7 +68,7 @@ class IncludedFile:
original_host = res._host original_host = res._host
original_task = res._task 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 original_task.loop:
if 'results' not in res._result: if 'results' not in res._result:
continue continue
@ -111,7 +112,7 @@ class IncludedFile:
templar = Templar(loader=loader, variables=task_vars) 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 include_file = None
if original_task: if original_task:
if original_task.static: if original_task.static:

View file

@ -21,6 +21,7 @@ __metaclass__ = type
import os import os
import ansible.constants as C
from ansible.errors import AnsibleParserError, AnsibleAssertionError from ansible.errors import AnsibleParserError, AnsibleAssertionError
from ansible.module_utils._text import to_bytes from ansible.module_utils._text import to_bytes
from ansible.module_utils.six import iteritems, string_types 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 new_ds.ansible_pos = ds.ansible_pos
for (k, v) in iteritems(ds): 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) self._preprocess_import(ds, new_ds, k, v)
else: else:
# some basic error checking, to make sure vars are properly # some basic error checking, to make sure vars are properly

View file

@ -20,6 +20,7 @@ __metaclass__ = type
from os.path import basename from os.path import basename
import ansible.constants as C
from ansible.errors import AnsibleParserError from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.block import Block from ansible.playbook.block import Block
@ -127,7 +128,7 @@ class IncludeRole(TaskInclude):
if ir._role_name is None: if ir._role_name is None:
raise AnsibleParserError("'name' is a required field for %s." % ir.action, obj=data) 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) raise AnsibleParserError('Invalid options for %s: public' % ir.action, obj=data)
# validate bad args, otherwise we silently ignore # validate bad args, otherwise we silently ignore
@ -144,7 +145,7 @@ class IncludeRole(TaskInclude):
ir._from_files[from_key] = basename(args_value) ir._from_files[from_key] = basename(args_value)
apply_attrs = ir.args.get('apply', {}) 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) raise AnsibleParserError('Invalid options for %s: apply' % ir.action, obj=data)
elif not isinstance(apply_attrs, dict): elif not isinstance(apply_attrs, dict):
raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data) raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data)

View file

@ -153,7 +153,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch):
def __repr__(self): def __repr__(self):
''' returns a human readable representation of the task ''' ''' 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'] return "TASK: meta (%s)" % self.args['_raw_params']
else: else:
return "TASK: %s" % self.get_name() 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, # the command/shell/script modules used to support the `cmd` arg,
# which corresponds to what we now call _raw_params, so move that # which corresponds to what we now call _raw_params, so move that
# value over to _raw_params (assuming it is empty) # 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 'cmd' in args:
if args.get('_raw_params', '') != '': if args.get('_raw_params', '') != '':
raise AnsibleError("The 'cmd' argument cannot be used when other raw parameters are specified." 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, # 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 # 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. # 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." 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" " 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", " 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) env[k] = templar.template(v, convert_bare=False)
except AnsibleUndefinedVariable as e: except AnsibleUndefinedVariable as e:
error = to_native(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 # ignore as fact gathering is required for 'env' facts
return return
raise raise
@ -394,7 +394,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch):
all_vars = dict() all_vars = dict()
if self._parent: if self._parent:
all_vars.update(self._parent.get_include_params()) 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) all_vars.update(self.vars)
return all_vars return all_vars

View file

@ -76,7 +76,7 @@ class TaskInclude(Task):
# validate bad args, otherwise we silently ignore # validate bad args, otherwise we silently ignore
bad_opts = my_arg_names.difference(self.VALID_ARGS) 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) raise AnsibleParserError('Invalid options for %s: %s' % (task.action, ','.join(list(bad_opts))), obj=data)
if not task.args.get('_raw_params'): if not task.args.get('_raw_params'):
@ -85,7 +85,7 @@ class TaskInclude(Task):
raise AnsibleParserError('No file specified for %s' % task.action) raise AnsibleParserError('No file specified for %s' % task.action)
apply_attrs = task.args.get('apply', {}) 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) raise AnsibleParserError('Invalid options for %s: apply' % task.action, obj=data)
elif not isinstance(apply_attrs, dict): elif not isinstance(apply_attrs, dict):
raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data) 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) diff = set(ds.keys()).difference(self.VALID_INCLUDE_KEYWORDS)
for k in diff: for k in diff:
# This check doesn't handle ``include`` as we have no idea at this point if it is static or not # 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: if C.INVALID_TASK_ATTRIBUTE_FAILED:
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (k, self.__class__.__name__), obj=ds) raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (k, self.__class__.__name__), obj=ds)
else: else:
@ -117,7 +117,7 @@ class TaskInclude(Task):
we need to include the args of the include into the vars as we need to include the args of the include into the vars as
they are params to the included tasks. But ONLY for 'include' 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() all_vars = super(TaskInclude, self).get_vars()
else: else:
all_vars = dict() all_vars = dict()

View file

@ -248,7 +248,7 @@ class CallbackBase(AnsiblePlugin):
''' removes data from results for display ''' ''' removes data from results for display '''
# mostly controls that debug only outputs what it was meant to # 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: if 'msg' in result:
# msg should be alone # msg should be alone
for key in list(result.keys()): for key in list(result.keys()):

View file

@ -675,7 +675,7 @@ class StrategyBase:
host_list = self.get_task_hosts(iterator, original_host, original_task) 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']): for (var_name, var_value) in iteritems(result_item['ansible_facts']):
# find the host we're actually referring too here, which may # find the host we're actually referring too here, which may
# be a host that is not really in inventory at all # 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) # we set BOTH fact and nonpersistent_facts (aka hostvar)
# when fact is retrieved from cache in subsequent operations it will have the lower precedence, # 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 # 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()) 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()) 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']: if 'ansible_stats' in result_item and 'data' in result_item['ansible_stats'] and result_item['ansible_stats']['data']:

View file

@ -189,7 +189,7 @@ class StrategyModule(StrategyBase):
del self._blocked_hosts[host_name] del self._blocked_hosts[host_name]
continue continue
if task.action == 'meta': if task.action in C._ACTION_META:
self._execute_meta(task, play_context, iterator, target_host=host) self._execute_meta(task, play_context, iterator, target_host=host)
self._blocked_hosts[host_name] = False self._blocked_hosts[host_name] = False
else: else:

View file

@ -31,6 +31,7 @@ DOCUMENTATION = '''
author: Ansible Core Team author: Ansible Core Team
''' '''
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleAssertionError from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.executor.play_iterator import PlayIterator from ansible.executor.play_iterator import PlayIterator
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
@ -271,7 +272,7 @@ class StrategyModule(StrategyBase):
# corresponding action plugin # corresponding action plugin
action = None 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 # for the linear strategy, we run meta tasks just once and for
# all hosts currently being iterated over rather than one host # all hosts currently being iterated over rather than one host
results.extend(self._execute_meta(task, play_context, iterator, host)) results.extend(self._execute_meta(task, play_context, iterator, host))
@ -409,7 +410,7 @@ class StrategyModule(StrategyBase):
for res in results: for res in results:
# execute_meta() does not set 'failed' in the TaskResult # execute_meta() does not set 'failed' in the TaskResult
# so we skip checking it with the meta tasks and look just at the iterator # 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) failed_hosts.append(res._host.name)
elif res.is_unreachable(): elif res.is_unreachable():
unreachable_hosts.append(res._host.name) unreachable_hosts.append(res._host.name)

33
lib/ansible/utils/fqcn.py Normal file
View file

@ -0,0 +1,33 @@
# (c) 2020, Felix Fontein <felix@fontein.de>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
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

View file

@ -219,7 +219,7 @@ class VariableManager:
# if we have a task in this context, and that task has a role, make # 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 # 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 # (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()), all_vars = _combine_and_track(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()),
"role '%s' defaults" % task._role.name) "role '%s' defaults" % task._role.name)

View file

@ -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]

View file

@ -9,3 +9,9 @@ for i in 1 2 3; do
grep "ok: \[localhost\] => (item=$i)" out grep "ok: \[localhost\] => (item=$i)" out
grep "\"item\": $i" out grep "\"item\": $i" out
done 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

View file

@ -49,23 +49,29 @@ test "$(grep -E -c 'Expected a string for vars_from but got' test_include_role_v
## Max Recursion Depth ## Max Recursion Depth
# https://github.com/ansible/ansible/issues/23609 # 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.yml -i inventory "$@"
ANSIBLE_STRATEGY='linear' ansible-playbook test_role_recursion_fqcn.yml -i inventory "$@"
## Nested tasks ## Nested tasks
# https://github.com/ansible/ansible/issues/34782 # 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.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.yml -i inventory "$@"
ANSIBLE_STRATEGY='free' ansible-playbook test_nested_tasks_fqcn.yml -i inventory "$@"
## Tons of top level include_tasks ## Tons of top level include_tasks
# https://github.com/ansible/ansible/issues/36053 # https://github.com/ansible/ansible/issues/36053
# Fixed by https://github.com/ansible/ansible/pull/36075 # Fixed by https://github.com/ansible/ansible/pull/36075
gen_task_files gen_task_files
ANSIBLE_STRATEGY='linear' ansible-playbook test_copious_include_tasks.yml -i inventory "$@" 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.yml -i inventory "$@"
ANSIBLE_STRATEGY='free' ansible-playbook test_copious_include_tasks_fqcn.yml -i inventory "$@"
rm -f tasks/hello/*.yml rm -f tasks/hello/*.yml
# Inlcuded tasks should inherit attrs from non-dynamic blocks in parent chain # Inlcuded tasks should inherit attrs from non-dynamic blocks in parent chain
# https://github.com/ansible/ansible/pull/38827 # 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.yml -i inventory "$@"
ANSIBLE_STRATEGY='linear' ansible-playbook test_grandparent_inheritance_fqcn.yml -i inventory "$@"
# undefined_var # undefined_var
ANSIBLE_STRATEGY='linear' ansible-playbook undefined_var/playbook.yml -i inventory "$@" 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_HOST_PATTERN_MISMATCH=error ansible-playbook empty_group_warning/playbook.yml
ansible-playbook test_include_loop.yml "$@" ansible-playbook test_include_loop.yml "$@"
ansible-playbook test_include_loop_fqcn.yml "$@"

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,7 @@
- name: Test max recursion depth
hosts: testhost
tasks:
- ansible.builtin.import_role:
name: role1
tasks_from: r1t01.yml

View file

@ -65,12 +65,12 @@
- "testing == 456" - "testing == 456"
- "base_dir == 'services'" - "base_dir == 'services'"
- "webapp_containers == 10" - "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/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/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/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/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[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 - name: include every directory in vars except files matching webapp.yml
include_vars: include_vars:
@ -85,7 +85,7 @@
that: that:
- "testing == 789" - "testing == 789"
- "base_dir == 'environments/development'" - "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(' ') }}'" - "'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(' ') }}'" - "'file_without_extension' not in '{{ include_without_webapp.ansible_included_var_files | join(' ') }}'"
@ -152,3 +152,13 @@
assert: assert:
that: that:
- "'Could not find file' in include_with_non_existent_file.message" - "'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"

View file

@ -0,0 +1,3 @@
---
service_name_fqcn: 'my_custom_service'
service_name_tmpl_fqcn: '{{ service_name_fqcn }}'

View file

@ -0,0 +1,2 @@
- set_fact:
inner_fqcn: "reached"

View file

@ -4,3 +4,6 @@
- include: inner.yml - include: inner.yml
with_items: with_items:
- '1' - '1'
- ansible.builtin.include: inner_fqcn.yml
with_items:
- '1'

View file

@ -7,3 +7,4 @@
- assert: - assert:
that: that:
- "inner == 'reached'" - "inner == 'reached'"
- "inner_fqcn == 'reached'"

View file

@ -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 '"skip_reason": "end_host conditional evaluated to False, continuing execution for testhost"' <<< "$out"
grep -q "play not ended for testhost" <<< "$out" grep -q "play not ended for testhost" <<< "$out"
grep -qv "play not ended for testhost2" <<< "$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 done
# test end_host meta task, on all hosts # 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 -q "META: ending play for testhost2" <<< "$out"
grep -qv "play not ended for testhost" <<< "$out" grep -qv "play not ended for testhost" <<< "$out"
grep -qv "play not ended for testhost2" <<< "$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 done
# test end_play meta task # test end_play meta task
@ -29,4 +44,9 @@ for test_strategy in linear free; do
grep -q "META: ending play" <<< "$out" grep -q "META: ending play" <<< "$out"
grep -qv 'Failed to end using end_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 done

View file

@ -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 }}"

View file

@ -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 }}"

View file

@ -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'

View file

@ -17,3 +17,19 @@
assert: assert:
that: that:
- dig_list == ['one', 'two', 'three', 'four'] - 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']

View file

@ -5,3 +5,6 @@
- name: set ssh jump host args - name: set ssh jump host args
set_fact: set_fact:
ansible_ssh_common_args: "-o ProxyCommand='ssh -W %h:%p -q root@localhost'" 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'"

View file

@ -18,3 +18,18 @@
- this_is_also_string == False - this_is_also_string == False
- this_is_another_string == False - this_is_another_string == False
- this_is_more_strings == 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

View file

@ -18,3 +18,18 @@
- this_is_also_string == 'no' - this_is_also_string == 'no'
- this_is_another_string == 'no' - this_is_another_string == 'no'
- this_is_more_strings == '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'

View file

@ -45,6 +45,49 @@
that: that:
- fact_not_cached == 'this_should_not_be_cached!' - 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 - name: the second play
hosts: localhost hosts: localhost
tasks: tasks:
@ -57,26 +100,43 @@
that: that:
- ansible_foobar == 'foobar_from_set_fact_cacheable' - 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 hosts: localhost
tasks: tasks:
- name: show nodename fact after second play - name: show nodename fact after second play
debug: debug:
var: ansible_nodename 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 hosts: localhost
vars: vars:
ansible_nodename: 'nodename_from_play_vars' ansible_nodename: 'nodename_from_play_vars'
ansible_os_family: 'os_family_from_play_vars'
tasks: tasks:
- name: show nodename fact after second play - name: show nodename fact after second play
debug: debug:
var: ansible_nodename 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 - name: verify ansible_nodename from vars overrides the fact
hosts: localhost hosts: localhost
vars: vars:
ansible_nodename: 'nodename_from_play_vars' ansible_nodename: 'nodename_from_play_vars'
ansible_os_family: 'os_family_from_play_vars'
tasks: tasks:
- name: show nodename fact - name: show nodename fact
debug: debug:
@ -87,7 +147,16 @@
that: that:
- ansible_nodename == 'nodename_from_play_vars' - 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 hosts: localhost
tasks: tasks:
- name: set a persistent fact nodename - name: set a persistent fact nodename
@ -103,10 +172,24 @@
that: that:
- ansible_nodename == 'nodename_from_set_fact_cacheable' - 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 hosts: localhost
vars: vars:
ansible_nodename: 'nodename_from_play_vars' ansible_nodename: 'nodename_from_play_vars'
ansible_os_family: 'os_family_from_play_vars'
tasks: tasks:
- name: show nodename fact - name: show nodename fact
debug: debug:
@ -117,10 +200,20 @@
that: that:
- ansible_nodename == 'nodename_from_set_fact_cacheable' - 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 hosts: localhost
vars: vars:
ansible_nodename: 'nodename_from_play_vars' ansible_nodename: 'nodename_from_play_vars'
ansible_os_family: 'os_family_from_play_vars'
tasks: tasks:
- name: show nodename fact - name: show nodename fact
debug: debug:
@ -131,7 +224,16 @@
that: that:
- ansible_nodename == 'nodename_from_set_fact_cacheable' - 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 hosts: localhost
tasks: tasks:
- name: set a persistent fact nodename - name: set a persistent fact nodename
@ -148,11 +250,26 @@
that: that:
- ansible_nodename == 'nodename_from_set_fact_cacheable' - 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 hosts: localhost
vars: vars:
ansible_nodename: 'nodename_from_play_vars' ansible_nodename: 'nodename_from_play_vars'
ansible_os_family: 'os_family_from_play_vars'
tasks: tasks:
- name: show nodename fact - name: show nodename fact
debug: debug:
@ -163,10 +280,20 @@
that: that:
- ansible_nodename == 'nodename_from_set_fact_cacheable' - 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 - name: the fourth play
hosts: localhost hosts: localhost
vars: vars:
ansible_foobar: 'foobar_from_play_vars' ansible_foobar: 'foobar_from_play_vars'
ansible_foobar_fqcn: 'foobar_fqcn_from_play_vars'
tasks: tasks:
- name: show example fact - name: show example fact
debug: debug:
@ -181,3 +308,17 @@
assert: assert:
that: that:
- ansible_example == 'foobar_from_set_fact_cacheable' - 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'

View file

@ -28,3 +28,30 @@
assert: assert:
that: that:
- fact_not_cached is undefined - 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

View file

@ -19,3 +19,21 @@
assert: assert:
that: that:
- ansible_foobar_not_cached is undefined - 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