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:
James Cammarata
2017-06-06 16:39:48 -05:00
committed by GitHub
parent 3549391673
commit 483df9c5f8
32 changed files with 812 additions and 569 deletions

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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