Move tasks/blocks to a single parent model

This commit is contained in:
James Cammarata 2016-08-01 14:10:02 -05:00
parent d2b3b2c03e
commit 06d4f4ad0e
12 changed files with 182 additions and 165 deletions

View file

@ -244,14 +244,14 @@ class PlayIterator:
if ra != rb:
return True
else:
return old_s.cur_dep_chain != task._block.get_dep_chain()
return old_s.cur_dep_chain != task.get_dep_chain()
if task and task._role:
# if we had a current role, mark that role as completed
if s.cur_role and _roles_are_different(task._role, s.cur_role) and host.name in s.cur_role._had_task_run and not peek:
s.cur_role._completed[host.name] = True
s.cur_role = task._role
s.cur_dep_chain = task._block.get_dep_chain()
s.cur_dep_chain = task.get_dep_chain()
if not peek:
self._host_states[host.name] = s

View file

@ -85,6 +85,8 @@ class Base:
# and initialize the base attributes
self._initialize_base_attributes()
self._cached_parent_attrs = dict()
# and init vars, avoid using defaults in field declaration as it lives across plays
self.vars = dict()
@ -111,13 +113,21 @@ class Base:
@staticmethod
def _generic_g(prop_name, self):
method = "_get_attr_%s" % prop_name
if hasattr(self, method):
try:
value = getattr(self, method)()
else:
except AttributeError:
try:
value = self._attributes[prop_name]
if value is None and hasattr(self, '_get_parent_attribute'):
if value is None:
try:
if prop_name in self._cached_parent_attrs:
value = self._cached_parent_attrs[prop_name]
else:
value = self._get_parent_attribute(prop_name)
# FIXME: temporarily disabling due to bugs
#self._cached_parent_attrs[prop_name] = value
except AttributeError:
pass
except KeyError:
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
@ -151,6 +161,20 @@ class Base:
BASE_ATTRIBUTES[self.__class__.__name__] = base_attributes
return base_attributes
def dump_me(self, depth=0):
if depth == 0:
print("DUMPING OBJECT ------------------------------------------------------")
print("%s- %s (%s)" % (" " * depth, self.__class__.__name__, self))
if hasattr(self, '_parent') and self._parent:
self._parent.dump_me(depth+2)
dep_chain = self._parent.get_dep_chain()
print("%s^ dep chain: %s" % (" "*(depth+2), dep_chain))
if dep_chain:
for dep in dep_chain:
dep.dump_me(depth+2)
if hasattr(self, '_play') and self._play:
self._play.dump_me(depth+2)
def _initialize_base_attributes(self):
# each class knows attributes set upon it, see Task.py for example
self._attributes = dict()

View file

@ -44,16 +44,15 @@ class Block(Base, Become, Conditional, Taggable):
def __init__(self, play=None, parent_block=None, role=None, task_include=None, use_handlers=False, implicit=False):
self._play = play
self._role = role
self._task_include = None
self._parent_block = None
self._parent = None
self._dep_chain = None
self._use_handlers = use_handlers
self._implicit = implicit
if task_include:
self._task_include = task_include
self._parent = task_include
elif parent_block:
self._parent_block = parent_block
self._parent = parent_block
super(Block, self).__init__()
@ -65,10 +64,8 @@ class Block(Base, Become, Conditional, Taggable):
all_vars = self.vars.copy()
if self._parent_block:
all_vars.update(self._parent_block.get_vars())
if self._task_include:
all_vars.update(self._task_include.get_vars())
if self._parent:
all_vars.update(self._parent.get_vars())
return all_vars
@ -109,7 +106,7 @@ class Block(Base, Become, Conditional, Taggable):
play=self._play,
block=self,
role=self._role,
task_include=self._task_include,
task_include=None,
variable_manager=self._variable_manager,
loader=self._loader,
use_handlers=self._use_handlers,
@ -124,7 +121,7 @@ class Block(Base, Become, Conditional, Taggable):
play=self._play,
block=self,
role=self._role,
task_include=self._task_include,
task_include=None,
variable_manager=self._variable_manager,
loader=self._loader,
use_handlers=self._use_handlers,
@ -139,7 +136,7 @@ class Block(Base, Become, Conditional, Taggable):
play=self._play,
block=self,
role=self._role,
task_include=self._task_include,
task_include=None,
variable_manager=self._variable_manager,
loader=self._loader,
use_handlers=self._use_handlers,
@ -149,10 +146,8 @@ class Block(Base, Become, Conditional, Taggable):
def get_dep_chain(self):
if self._dep_chain is None:
if self._parent_block:
return self._parent_block.get_dep_chain()
elif self._task_include:
return self._task_include._block.get_dep_chain()
if self._parent:
return self._parent.get_dep_chain()
else:
return None
else:
@ -162,12 +157,18 @@ class Block(Base, Become, Conditional, Taggable):
def _dupe_task_list(task_list, new_block):
new_task_list = []
for task in task_list:
if isinstance(task, Block):
new_task = task.copy(exclude_parent=True)
new_task._parent_block = new_block
if task._parent:
new_task._parent = task._parent.copy(exclude_tasks=True)
# go up the parentage tree until we find an
# object without a parent and make this new
# block their parent
cur_obj = new_task
while cur_obj._parent:
cur_obj = cur_obj._parent
cur_obj._parent = new_block
else:
new_task = task.copy(exclude_block=True)
new_task._block = new_block
new_task._parent = new_block
new_task_list.append(new_task)
return new_task_list
@ -175,27 +176,22 @@ class Block(Base, Become, Conditional, Taggable):
new_me._play = self._play
new_me._use_handlers = self._use_handlers
if self._dep_chain:
if self._dep_chain is not None:
new_me._dep_chain = self._dep_chain[:]
new_me._parent = None
if self._parent and not exclude_parent:
new_me._parent = self._parent.copy(exclude_tasks=exclude_tasks)
if not exclude_tasks:
new_me.block = _dupe_task_list(self.block or [], new_me)
new_me.rescue = _dupe_task_list(self.rescue or [], new_me)
new_me.always = _dupe_task_list(self.always or [], new_me)
new_me._parent_block = None
if self._parent_block and not exclude_parent:
new_me._parent_block = self._parent_block#.copy(exclude_tasks=exclude_tasks)
new_me._role = None
if self._role:
new_me._role = self._role
new_me._task_include = None
if self._task_include:
new_me._task_include = self._task_include#.copy(exclude_block=True)
#new_me._task_include._block = self._task_include._block.copy(exclude_tasks=True)
return new_me
def serialize(self):
@ -213,10 +209,9 @@ class Block(Base, Become, Conditional, Taggable):
if self._role is not None:
data['role'] = self._role.serialize()
#if self._task_include is not None:
# data['task_include'] = self._task_include.serialize()
if self._parent_block is not None:
data['parent_block'] = self._parent_block.copy(exclude_tasks=True).serialize()
if self._parent is not None:
data['parent'] = self._parent.copy(exclude_tasks=True).serialize()
data['parent_type'] = self._parent.__class__.__name__
return data
@ -226,7 +221,10 @@ class Block(Base, Become, Conditional, Taggable):
serialize method
'''
# import is here to avoid import loops
from ansible.playbook.task import Task
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.handler_task_include import HandlerTaskInclude
# we don't want the full set of attributes (the task lists), as that
# would lead to a serialize/deserialize loop
@ -243,19 +241,18 @@ class Block(Base, Become, Conditional, Taggable):
r.deserialize(role_data)
self._role = r
# if there was a serialized task include, unpack it too
ti_data = data.get('task_include')
if ti_data:
ti = Task()
ti.deserialize(ti_data)
self._task_include = ti
pb_data = data.get('parent_block')
if pb_data:
pb = Block()
pb.deserialize(pb_data)
self._parent_block = pb
self._dep_chain = self._parent_block.get_dep_chain()
parent_data = data.get('parent')
if parent_data:
parent_type = data.get('parent_type')
if parent_type == 'Block':
p = Block()
elif parent_type == 'TaskInclude':
p = TaskInclude()
elif parent_type == 'HandlerTaskInclude':
p = HandlerTaskInclude()
p.deserialize(pb_data)
self._parent = p
self._dep_chain = self._parent.get_dep_chain()
def evaluate_conditional(self, templar, all_vars):
dep_chain = self.get_dep_chain()
@ -263,24 +260,18 @@ class Block(Base, Become, Conditional, Taggable):
for dep in dep_chain:
if not dep.evaluate_conditional(templar, all_vars):
return False
if self._task_include is not None:
if not self._task_include.evaluate_conditional(templar, all_vars):
return False
if self._parent_block is not None:
if not self._parent_block.evaluate_conditional(templar, all_vars):
if self._parent is not None:
if not self._parent.evaluate_conditional(templar, all_vars):
return False
return super(Block, self).evaluate_conditional(templar, all_vars)
def set_loader(self, loader):
self._loader = loader
if self._parent_block:
self._parent_block.set_loader(loader)
if self._parent:
self._parent.set_loader(loader)
elif self._role:
self._role.set_loader(loader)
if self._task_include:
self._task_include.set_loader(loader)
dep_chain = self.get_dep_chain()
if dep_chain:
for dep in dep_chain:
@ -295,14 +286,8 @@ class Block(Base, Become, Conditional, Taggable):
try:
value = self._attributes[attr]
if self._parent_block and (value is None or extend):
parent_value = getattr(self._parent_block, attr, None)
if extend:
value = self._extend_value(value, parent_value)
else:
value = parent_value
if self._task_include and (value is None or extend):
parent_value = getattr(self._task_include, attr, None)
if self._parent and (value is None or extend):
parent_value = getattr(self._parent, attr, None)
if extend:
value = self._extend_value(value, parent_value)
else:
@ -383,3 +368,8 @@ class Block(Base, Become, Conditional, Taggable):
def has_tasks(self):
return len(self.block) > 0 or len(self.rescue) > 0 or len(self.always) > 0
def get_include_params(self):
if self._parent:
return self._parent.get_include_params()
else:
return dict()

View file

@ -46,9 +46,9 @@ def load_list_of_blocks(ds, play, parent_block=None, role=None, task_include=Non
block_list = []
if ds:
for block in ds:
for block_ds in ds:
b = Block.load(
block,
block_ds,
play=play,
parent_block=parent_block,
role=role,
@ -96,7 +96,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
play=play,
parent_block=block,
role=role,
task_include=None,
task_include=task_include,
use_handlers=use_handlers,
variable_manager=variable_manager,
loader=loader,
@ -105,9 +105,19 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
else:
if 'include' in task_ds:
if use_handlers:
t = HandlerTaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
include_class = HandlerTaskInclude
else:
t = TaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
include_class = TaskInclude
t = include_class.load(
task_ds,
block=block,
role=role,
task_include=None,
variable_manager=variable_manager,
loader=loader
)
all_vars = variable_manager.get_vars(loader=loader, play=play, task=t)
templar = Templar(loader=loader, variables=all_vars)
@ -134,6 +144,9 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
parent_include = task_include
cumulative_path = None
while parent_include is not None:
if not isinstance(parent_include, TaskInclude):
parent_include = parent_include._parent
continue
parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params')))
if cumulative_path is None:
cumulative_path = parent_include_dir
@ -149,7 +162,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
if os.path.exists(include_file):
break
else:
parent_include = parent_include._task_include
parent_include = parent_include._parent
else:
try:
include_target = templar.template(t.args['_raw_params'])
@ -195,8 +208,8 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
included_blocks = load_list_of_blocks(
data,
play=play,
parent_block=block,
task_include=t,
parent_block=None,
task_include=t.copy(),
role=role,
use_handlers=use_handlers,
loader=loader,
@ -214,7 +227,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
if len(t.tags) > 0:
raise AnsibleParserError(
"Include tasks should not specify tags in more than one way (both via args and directly on the task). " \
" Mixing tag specify styles is prohibited for whole import hierarchy, not only for single import statement",
"Mixing styles in which tags are specified is prohibited for whole import hierarchy, not only for single import statement",
obj=task_ds,
suppress_extended_error=True,
)

View file

@ -22,6 +22,7 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleError
from ansible.playbook.task_include import TaskInclude
from ansible.template import Templar
try:
@ -86,16 +87,20 @@ class IncludedFile:
if loop_var in include_result:
task_vars[loop_var] = include_variables[loop_var] = include_result[loop_var]
include_file = None
if original_task:
if original_task.static:
continue
if original_task._task_include:
if original_task._parent:
# handle relative includes by walking up the list of parent include
# tasks and checking the relative result to see if it exists
parent_include = original_task._task_include
parent_include = original_task._parent
cumulative_path = None
while parent_include is not None:
if not isinstance(parent_include, TaskInclude):
parent_include = parent_include._parent
continue
parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params')))
if cumulative_path is None:
cumulative_path = parent_include_dir
@ -111,14 +116,14 @@ class IncludedFile:
if os.path.exists(include_file):
break
else:
parent_include = parent_include._task_include
elif original_task._role:
parent_include = parent_include._parent
if include_file is None:
if original_task._role:
include_target = templar.template(include_result['include'])
include_file = loader.path_dwim_relative(original_task._role._role_path, 'tasks', include_target)
else:
include_file = loader.path_dwim(include_result['include'])
else:
include_file = loader.path_dwim(include_result['include'])
include_file = templar.template(include_file)
inc_file = IncludedFile(include_file, include_variables, original_task)

View file

@ -353,7 +353,9 @@ class Role(Base, Become, Conditional, Taggable):
block_list.extend(dep_blocks)
for task_block in self._task_blocks:
new_task_block = task_block.copy()
new_task_block = task_block.copy(exclude_parent=True)
if task_block._parent:
new_task_block._parent = task_block._parent.copy()
new_task_block._dep_chain = new_dep_chain
new_task_block._play = play
block_list.append(new_task_block)

View file

@ -92,9 +92,13 @@ class Task(Base, Conditional, Taggable, Become):
def __init__(self, block=None, role=None, task_include=None):
''' constructors a task, without the Task.load classmethod, it will be pretty blank '''
self._block = block
self._role = role
self._task_include = task_include
self._parent = None
if task_include:
self._parent = task_include
else:
self._parent = block
super(Task, self).__init__()
@ -242,10 +246,8 @@ class Task(Base, Conditional, Taggable, Become):
the block and task include (if any) to which this task belongs.
'''
if self._block:
self._block.post_validate(templar)
if self._task_include:
self._task_include.post_validate(templar)
if self._parent:
self._parent.post_validate(templar)
super(Task, self).post_validate(templar)
@ -304,10 +306,8 @@ class Task(Base, Conditional, Taggable, Become):
def get_vars(self):
all_vars = dict()
if self._block:
all_vars.update(self._block.get_vars())
if self._task_include:
all_vars.update(self._task_include.get_vars())
if self._parent:
all_vars.update(self._parent.get_vars())
all_vars.update(self.vars)
@ -320,56 +320,55 @@ class Task(Base, Conditional, Taggable, Become):
def get_include_params(self):
all_vars = dict()
if self._task_include:
all_vars.update(self._task_include.get_include_params())
if self._parent:
all_vars.update(self._parent.get_include_params())
if self.action == 'include':
all_vars.update(self.vars)
return all_vars
def copy(self, exclude_block=False, exclude_tasks=False):
def copy(self, exclude_parent=False, exclude_tasks=False):
new_me = super(Task, self).copy()
new_me._block = None
if self._block and not exclude_block:
new_me._block = self._block#.copy(exclude_tasks=exclude_tasks)
new_me._parent = None
if self._parent and not exclude_parent:
new_me._parent = self._parent.copy(exclude_tasks=exclude_tasks)
new_me._role = None
if self._role:
new_me._role = self._role
new_me._task_include = None
if self._task_include:
new_me._task_include = self._task_include#.copy(exclude_block=True)
#new_me._task_include._block = self._task_include._block.copy(exclude_tasks=True)
return new_me
def serialize(self):
data = super(Task, self).serialize()
if self._block:
data['block'] = self._block.serialize()
if self._parent:
data['parent'] = self._parent.serialize()
data['parent_type'] = self._parent.__class__.__name__
if self._role:
data['role'] = self._role.serialize()
if self._task_include:
data['task_include'] = self._task_include.serialize()
return data
def deserialize(self, data):
# import is here to avoid import loops
#from ansible.playbook.task_include import TaskInclude
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.handler_task_include import HandlerTaskInclude
block_data = data.get('block')
if block_data:
b = Block()
b.deserialize(block_data)
self._block = b
del data['block']
parent_data = data.get('parent', None)
if parent_data:
parent_type = data.get('parent_type')
if parent_type == 'Block':
p = Block()
elif parent_type == 'TaskInclude':
p = TaskInclude()
elif parent_type == 'HandlerTaskInclude':
p = HandlerTaskInclude()
p.deserialize(parent_data)
self._parent = p
del data['parent']
role_data = data.get('role')
if role_data:
@ -378,22 +377,11 @@ class Task(Base, Conditional, Taggable, Become):
self._role = r
del data['role']
ti_data = data.get('task_include')
if ti_data:
#ti = TaskInclude()
ti = Task()
ti.deserialize(ti_data)
self._task_include = ti
del data['task_include']
super(Task, self).deserialize(data)
def evaluate_conditional(self, templar, all_vars):
if self._block is not None:
if not self._block.evaluate_conditional(templar, all_vars):
return False
if self._task_include is not None:
if not self._task_include.evaluate_conditional(templar, all_vars):
if self._parent is not None:
if not self._parent.evaluate_conditional(templar, all_vars):
return False
return super(Task, self).evaluate_conditional(templar, all_vars)
@ -406,10 +394,8 @@ class Task(Base, Conditional, Taggable, Become):
self._loader = loader
if self._block:
self._block.set_loader(loader)
if self._task_include:
self._task_include.set_loader(loader)
if self._parent:
self._parent.set_loader(loader)
def _get_parent_attribute(self, attr, extend=False):
'''
@ -419,14 +405,8 @@ class Task(Base, Conditional, Taggable, Become):
try:
value = self._attributes[attr]
if self._block and (value is None or extend):
parent_value = getattr(self._block, attr, None)
if extend:
value = self._extend_value(value, parent_value)
else:
value = parent_value
if self._task_include and (value is None or extend):
parent_value = getattr(self._task_include, attr, None)
if self._parent and (value is None or extend):
parent_value = getattr(self._parent, attr, None)
if extend:
value = self._extend_value(value, parent_value)
else:
@ -458,6 +438,11 @@ class Task(Base, Conditional, Taggable, Become):
def _get_attr_loop_control(self):
return self._attributes['loop_control']
def get_dep_chain(self):
if self._parent:
return self._parent.get_dep_chain()
else:
return None
def get_search_path(self):
'''
@ -466,7 +451,7 @@ class Task(Base, Conditional, Taggable, Become):
'''
path_stack = []
dep_chain = self._block.get_dep_chain()
dep_chain = self.get_dep_chain()
# inside role: add the dependency chain from current to dependant
if dep_chain:
path_stack.extend(reversed([x._role_path for x in dep_chain]))

View file

@ -55,10 +55,8 @@ class TaskInclude(Task):
they are params to the included tasks.
'''
all_vars = dict()
if self._block:
all_vars.update(self._block.get_vars())
if self._task_include:
all_vars.update(self._task_include.get_vars())
if self._parent:
all_vars.update(self._parent.get_vars())
all_vars.update(self.vars)
all_vars.update(self.args)

View file

@ -574,9 +574,9 @@ class StrategyBase:
block_list = load_list_of_blocks(
data,
play=included_file._task._block._play,
play=iterator._play,
parent_block=None,
task_include=included_file._task,
task_include=None,
role=included_file._task._role,
use_handlers=is_handler,
loader=self._loader,
@ -602,9 +602,9 @@ class StrategyBase:
# set the vars for this task from those specified as params to the include
for b in block_list:
# first make a copy of the including task, so that each has a unique copy to modify
b._task_include = b._task_include.copy()
b._parent = included_file._task.copy()
# then we create a temporary set of vars to ensure the variable reference is unique
temp_vars = b._task_include.vars.copy()
temp_vars = b._parent.vars.copy()
temp_vars.update(included_file._args.copy())
# pop tags out of the include args, if they were specified there, and assign
# them to the include. If the include already had tags specified, we raise an
@ -613,12 +613,12 @@ class StrategyBase:
if isinstance(tags, string_types):
tags = tags.split(',')
if len(tags) > 0:
if len(b._task_include.tags) > 0:
if len(b._parent.tags) > 0:
raise AnsibleParserError("Include tasks should not specify tags in more than one way (both via args and directly on the task). Mixing tag specify styles is prohibited for whole import hierarchy, not only for single import statement",
obj=included_file._task._ds)
display.deprecated("You should not specify tags in the include parameters. All tags should be specified using the task-level option")
b._task_include.tags = tags
b._task_include.vars = temp_vars
b._parent.tags = tags
b._parent.vars = temp_vars
# finally, send the callback and return the list of blocks loaded
self._tqm.send_callback('v2_playbook_on_include', included_file)

View file

@ -316,7 +316,7 @@ class StrategyModule(StrategyBase):
final_block = new_block.filter_tagged_tasks(play_context, task_vars)
display.debug("done filtering new block on tags")
noop_block = Block(parent_block=task._block)
noop_block = Block(parent_block=task._parent)
noop_block.block = [noop_task for t in new_block.block]
noop_block.always = [noop_task for t in new_block.always]
noop_block.rescue = [noop_task for t in new_block.rescue]

View file

@ -237,7 +237,7 @@ class VariableManager:
# 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 and task._role is not None:
all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task._block.get_dep_chain()))
all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()))
if host:
# next, if a host is specified, we load any vars from group_vars
@ -334,7 +334,7 @@ class VariableManager:
# vars (which will look at parent blocks/task includes)
if task:
if task._role:
all_vars = combine_vars(all_vars, task._role.get_vars(task._block._dep_chain, include_params=False))
all_vars = combine_vars(all_vars, task._role.get_vars(task.get_dep_chain(), include_params=False))
all_vars = combine_vars(all_vars, task.get_vars())
# next, we merge in the vars cache (include vars) and nonpersistent
@ -346,7 +346,7 @@ class VariableManager:
# next, we merge in role params and task include params
if task:
if task._role:
all_vars = combine_vars(all_vars, task._role.get_role_params(task._block.get_dep_chain()))
all_vars = combine_vars(all_vars, task._role.get_role_params(task.get_dep_chain()))
# special case for include tasks, where the include params
# may be specified in the vars field for the task, which should

View file

@ -121,7 +121,7 @@ class TestPlayIterator(unittest.TestCase):
# lookup up an original task
target_task = p._entries[0].tasks[0].block[0]
task_copy = target_task.copy(exclude_block=True)
task_copy = target_task.copy(exclude_parent=True)
found_task = itr.get_original_task(hosts[0], task_copy)
self.assertEqual(target_task, found_task)