mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 13:52:54 +00:00
Imports and includes (#25399)
Initial commit to split includes into static imports/dynamic includes
This implements the new include/import syntax for Ansible 2.4:
* include_{tasks,role,variables} = dynamic
* import_{playbook,tasks,role} = static
The old bare `include` will be considered deprecated, as will any use of the `static: {yes|no}` option.
This also adds docs for import/include and reorganizing the "Playbook Reuse" section of the documentation.
This commit is contained in:
@@ -432,7 +432,7 @@ class TaskExecutor:
|
||||
if self._loop_eval_error is not None:
|
||||
raise self._loop_eval_error
|
||||
# skip conditional exception in the case of includes as the vars needed might not be available except in the included tasks or due to tags
|
||||
if self._task.action not in ['include', 'include_role']:
|
||||
if self._task.action not in ['include', 'include_tasks', 'include_role']:
|
||||
raise
|
||||
|
||||
# Not skipping, if we had loop error raised earlier we need to raise it now to halt the execution of this task
|
||||
@@ -445,7 +445,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 == 'include':
|
||||
if self._task.action in ('include', 'include_tasks'):
|
||||
include_variables = self._task.args.copy()
|
||||
include_file = include_variables.pop('_raw_params', None)
|
||||
if not include_file:
|
||||
|
||||
@@ -83,11 +83,11 @@ class TaskQueueManager:
|
||||
self._callback_plugins = []
|
||||
self._start_at_done = False
|
||||
|
||||
# make sure the module path (if specified) is parsed and
|
||||
# added to the module_loader object
|
||||
if options.module_path is not None:
|
||||
for path in options.module_path.split(os.pathsep):
|
||||
module_loader.add_directory(path)
|
||||
# make sure any module paths (if specified) are added to the module_loader
|
||||
if isinstance(options.module_path, list):
|
||||
for path in options.module_path:
|
||||
if path is not None:
|
||||
module_loader.add_directory(path)
|
||||
|
||||
# a special flag to help us exit cleanly
|
||||
self._terminated = False
|
||||
|
||||
@@ -36,6 +36,10 @@ RAW_PARAM_MODULES = ([
|
||||
'script',
|
||||
'include',
|
||||
'include_vars',
|
||||
'include_tasks',
|
||||
'include_role',
|
||||
'import_tasks',
|
||||
'import_role',
|
||||
'add_host',
|
||||
'group_by',
|
||||
'set_fact',
|
||||
@@ -281,7 +285,7 @@ class ModuleArgsParser:
|
||||
|
||||
# walk the input dictionary to see we recognize a module name
|
||||
for (item, value) in iteritems(self._task_ds):
|
||||
if item in module_loader or item in ['meta', 'include', 'include_role']:
|
||||
if item in module_loader or item in ['meta', 'include', 'include_tasks', 'include_role', 'import_tasks', 'import_role']:
|
||||
# finding more than one module name is a problem
|
||||
if action is not None:
|
||||
raise AnsibleParserError("conflicting action statements: %s, %s" % (action, item), obj=self._task_ds)
|
||||
|
||||
@@ -89,7 +89,9 @@ 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 'include' in entry:
|
||||
if 'include' in entry or 'import_playbook' in entry:
|
||||
if 'include' in entry:
|
||||
display.deprecated("You should use 'import_playbook' instead of 'include' for playbook includes")
|
||||
pb = PlaybookInclude.load(entry, basedir=self._basedir, variable_manager=variable_manager, loader=self._loader)
|
||||
if pb is not None:
|
||||
self._entries.extend(pb._entries)
|
||||
|
||||
@@ -108,7 +108,11 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
||||
)
|
||||
task_list.append(t)
|
||||
else:
|
||||
if 'include' in task_ds:
|
||||
if 'include' in task_ds or 'import_tasks' in task_ds or 'include_tasks' in task_ds:
|
||||
if 'include' in task_ds:
|
||||
display.deprecated("The use of 'include' for tasks has been deprecated. "
|
||||
"Use 'import_tasks' for static inclusions or 'include_tasks' for dynamic inclusions")
|
||||
|
||||
if use_handlers:
|
||||
include_class = HandlerTaskInclude
|
||||
else:
|
||||
@@ -129,7 +133,13 @@ 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 t.static is not None:
|
||||
if 'include_tasks' in task_ds:
|
||||
is_static = False
|
||||
elif 'import_tasks' in task_ds:
|
||||
is_static = True
|
||||
elif t.static is not None:
|
||||
display.deprecated("The use of 'static' has been deprecated. "
|
||||
"Use 'import_role' for static inclusion, or 'include_role' for dynamic inclusion")
|
||||
is_static = t.static
|
||||
else:
|
||||
is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \
|
||||
@@ -138,7 +148,10 @@ 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:
|
||||
raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds)
|
||||
if 'import_tasks' in task_ds:
|
||||
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)
|
||||
|
||||
# we set a flag to indicate this include was static
|
||||
t.statically_loaded = True
|
||||
@@ -202,7 +215,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
||||
# the same fashion used by the on_include callback. We also do it here,
|
||||
# because the recursive nature of helper methods means we may be loading
|
||||
# nested includes, and we want the include order printed correctly
|
||||
display.vv("statically included: %s" % include_file)
|
||||
display.vv("statically imported: %s" % include_file)
|
||||
except AnsibleFileNotFound:
|
||||
if t.static or \
|
||||
C.DEFAULT_TASK_INCLUDES_STATIC or \
|
||||
@@ -267,8 +280,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
||||
else:
|
||||
task_list.append(t)
|
||||
|
||||
elif 'include_role' in task_ds:
|
||||
|
||||
elif 'include_role' in task_ds or 'import_role' in task_ds:
|
||||
ir = IncludeRole.load(
|
||||
task_ds,
|
||||
block=block,
|
||||
@@ -280,7 +292,11 @@ 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
|
||||
if 'import_role' in task_ds:
|
||||
is_static = True
|
||||
if ir.static is not None:
|
||||
display.deprecated("The use of 'static' for 'include_role' has been deprecated. "
|
||||
"Use 'import_role' for static inclusion, or 'include_role' for dynamic inclusion")
|
||||
is_static = ir.static
|
||||
else:
|
||||
display.debug('Determine if include_role is static')
|
||||
|
||||
@@ -64,7 +64,7 @@ class IncludedFile:
|
||||
original_host = res._host
|
||||
original_task = res._task
|
||||
|
||||
if original_task.action == 'include':
|
||||
if original_task.action in ('include', 'include_tasks'):
|
||||
if original_task.loop:
|
||||
if 'results' not in res._result:
|
||||
continue
|
||||
|
||||
@@ -35,7 +35,7 @@ from ansible.template import Templar
|
||||
class PlaybookInclude(Base, Conditional, Taggable):
|
||||
|
||||
_name = FieldAttribute(isa='string')
|
||||
_include = FieldAttribute(isa='string')
|
||||
_import_playbook = FieldAttribute(isa='string')
|
||||
_vars = FieldAttribute(isa='dict', default=dict())
|
||||
|
||||
@staticmethod
|
||||
@@ -66,7 +66,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
||||
# then we use the object to load a Playbook
|
||||
pb = Playbook(loader=loader)
|
||||
|
||||
file_name = templar.template(new_obj.include)
|
||||
file_name = templar.template(new_obj.import_playbook)
|
||||
if not os.path.isabs(file_name):
|
||||
file_name = os.path.join(basedir, file_name)
|
||||
|
||||
@@ -114,35 +114,35 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
||||
new_ds.ansible_pos = ds.ansible_pos
|
||||
|
||||
for (k, v) in iteritems(ds):
|
||||
if k == 'include':
|
||||
self._preprocess_include(ds, new_ds, k, v)
|
||||
if k in ('include', 'import_playbook'):
|
||||
self._preprocess_import(ds, new_ds, k, v)
|
||||
else:
|
||||
# some basic error checking, to make sure vars are properly
|
||||
# formatted and do not conflict with k=v parameters
|
||||
if k == 'vars':
|
||||
if 'vars' in new_ds:
|
||||
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
|
||||
raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
|
||||
elif not isinstance(v, dict):
|
||||
raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds)
|
||||
raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
|
||||
new_ds[k] = v
|
||||
|
||||
return super(PlaybookInclude, self).preprocess_data(new_ds)
|
||||
|
||||
def _preprocess_include(self, ds, new_ds, k, v):
|
||||
def _preprocess_import(self, ds, new_ds, k, v):
|
||||
'''
|
||||
Splits the include line up into filename and parameters
|
||||
Splits the playbook import line up into filename and parameters
|
||||
'''
|
||||
|
||||
if v is None:
|
||||
raise AnsibleParserError("include parameter is missing", obj=ds)
|
||||
raise AnsibleParserError("playbook import parameter is missing", obj=ds)
|
||||
|
||||
# The include line must include at least one item, which is the filename
|
||||
# to include. Anything after that should be regarded as a parameter to the include
|
||||
# The import_playbook line must include at least one item, which is the filename
|
||||
# to import. Anything after that should be regarded as a parameter to the import
|
||||
items = split_args(v)
|
||||
if len(items) == 0:
|
||||
raise AnsibleParserError("include statements must specify the file name to include", obj=ds)
|
||||
raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
|
||||
else:
|
||||
new_ds['include'] = items[0]
|
||||
new_ds['import_playbook'] = items[0]
|
||||
if len(items) > 1:
|
||||
# rejoin the parameter portion of the arguments and
|
||||
# then use parse_kv() to get a dict of params back
|
||||
@@ -150,5 +150,5 @@ class PlaybookInclude(Base, Conditional, Taggable):
|
||||
if 'tags' in params:
|
||||
new_ds['tags'] = params.pop('tags')
|
||||
if 'vars' in new_ds:
|
||||
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
|
||||
raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
|
||||
new_ds['vars'] = params
|
||||
|
||||
@@ -23,7 +23,7 @@ from os.path import basename
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.playbook.attribute import FieldAttribute
|
||||
from ansible.playbook.task import Task
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
from ansible.playbook.role import Role
|
||||
from ansible.playbook.role.include import RoleInclude
|
||||
|
||||
@@ -36,7 +36,7 @@ except ImportError:
|
||||
__all__ = ['IncludeRole']
|
||||
|
||||
|
||||
class IncludeRole(Task):
|
||||
class IncludeRole(TaskInclude):
|
||||
|
||||
"""
|
||||
A Role include is derived from a regular role to handle the special
|
||||
@@ -55,7 +55,6 @@ class IncludeRole(Task):
|
||||
|
||||
super(IncludeRole, self).__init__(block=block, role=role, task_include=task_include)
|
||||
|
||||
self.statically_loaded = False
|
||||
self._from_files = {}
|
||||
self._parent_role = role
|
||||
self._role_name = None
|
||||
|
||||
@@ -215,10 +215,10 @@ class Task(Base, Conditional, Taggable, Become):
|
||||
# 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 == '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:\nhttp://docs.ansible.com/ansible/playbooks_roles.html#task-include-files-and-encouraging-reuse\n\n "
|
||||
"for currently supported syntax regarding included files and variables", version="2.7")
|
||||
if action in ('include', 'include_tasks') 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:\nhttp://docs.ansible.com/ansible/playbooks_roles.html#task-include-files-and-encouraging-reuse\n\n"
|
||||
" for currently supported syntax regarding included files and variables", version="2.7")
|
||||
new_ds['vars'][k] = v
|
||||
else:
|
||||
new_ds[k] = v
|
||||
@@ -331,7 +331,7 @@ class Task(Base, Conditional, Taggable, Become):
|
||||
all_vars = dict()
|
||||
if self._parent:
|
||||
all_vars.update(self._parent.get_include_params())
|
||||
if self.action in ('include', 'include_role'):
|
||||
if self.action in ('include', 'include_tasks', 'include_role'):
|
||||
all_vars.update(self.vars)
|
||||
return all_vars
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.utils.color import colorize, hostcolor
|
||||
|
||||
@@ -80,7 +81,7 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||
self._clean_results(result._result, result._task.action)
|
||||
if result._task.action in ('include', 'include_role'):
|
||||
if isinstance(result._task, TaskInclude):
|
||||
return
|
||||
elif result._result.get('changed', False):
|
||||
if delegated_vars:
|
||||
@@ -194,7 +195,7 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
def v2_runner_item_on_ok(self, result):
|
||||
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||
if result._task.action in ('include', 'include_role'):
|
||||
if isinstance(result._task, TaskInclude):
|
||||
return
|
||||
elif result._result.get('changed', False):
|
||||
msg = 'changed'
|
||||
|
||||
@@ -535,7 +535,7 @@ class StrategyBase:
|
||||
if self._diff:
|
||||
self._tqm.send_callback('v2_on_file_diff', task_result)
|
||||
|
||||
if original_task.action not in ['include', 'include_role']:
|
||||
if not isinstance(original_task, TaskInclude):
|
||||
self._tqm._stats.increment('ok', original_host.name)
|
||||
if 'changed' in task_result._result and task_result._result['changed']:
|
||||
self._tqm._stats.increment('changed', original_host.name)
|
||||
|
||||
Reference in New Issue
Block a user