diff --git a/v2/ansible/playbook/base.py b/v2/ansible/playbook/base.py index 73eceba996..3a7879265e 100644 --- a/v2/ansible/playbook/base.py +++ b/v2/ansible/playbook/base.py @@ -324,6 +324,20 @@ class Base: # restore the UUID field setattr(self, '_uuid', data.get('uuid')) + def _extend_value(self, value, new_value): + ''' + Will extend the value given with new_value (and will turn both + into lists if they are not so already). The values are run through + a set to remove duplicate values. + ''' + + if not isinstance(value, list): + value = [ value ] + if not isinstance(new_value, list): + new_value = [ new_value ] + + return list(set(value + new_value)) + def __getstate__(self): return self.serialize() diff --git a/v2/ansible/playbook/block.py b/v2/ansible/playbook/block.py index b80deec6ed..e6ad8e5745 100644 --- a/v2/ansible/playbook/block.py +++ b/v2/ansible/playbook/block.py @@ -37,10 +37,11 @@ class Block(Base, Become, Conditional, Taggable): # similar to the 'else' clause for exceptions #_otherwise = FieldAttribute(isa='list') - def __init__(self, parent_block=None, role=None, task_include=None, use_handlers=False): - self._parent_block = parent_block + def __init__(self, play=None, parent_block=None, role=None, task_include=None, use_handlers=False): + self._play = play self._role = role self._task_include = task_include + self._parent_block = parent_block self._use_handlers = use_handlers self._dep_chain = [] @@ -65,8 +66,8 @@ class Block(Base, Become, Conditional, Taggable): return all_vars @staticmethod - def load(data, parent_block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): - b = Block(parent_block=parent_block, role=role, task_include=task_include, use_handlers=use_handlers) + def load(data, play, parent_block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): + b = Block(play=play, parent_block=parent_block, role=role, task_include=task_include, use_handlers=use_handlers) return b.load_data(data, variable_manager=variable_manager, loader=loader) def preprocess_data(self, ds): @@ -92,6 +93,7 @@ class Block(Base, Become, Conditional, Taggable): def _load_block(self, attr, ds): return load_list_of_tasks( ds, + play=self._play, block=self, role=self._role, task_include=self._task_include, @@ -103,6 +105,7 @@ class Block(Base, Become, Conditional, Taggable): def _load_rescue(self, attr, ds): return load_list_of_tasks( ds, + play=self._play, block=self, role=self._role, task_include=self._task_include, @@ -114,6 +117,7 @@ class Block(Base, Become, Conditional, Taggable): def _load_always(self, attr, ds): return load_list_of_tasks( ds, + play=self._play, block=self, role=self._role, task_include=self._task_include, @@ -126,6 +130,7 @@ class Block(Base, Become, Conditional, Taggable): #def _load_otherwise(self, attr, ds): # return load_list_of_tasks( # ds, + # play=self._play, # block=self, # role=self._role, # task_include=self._task_include, @@ -148,6 +153,7 @@ class Block(Base, Become, Conditional, Taggable): return new_task_list new_me = super(Block, self).copy() + new_me._play = self._play new_me._use_handlers = self._use_handlers new_me._dep_chain = self._dep_chain[:] @@ -248,24 +254,44 @@ class Block(Base, Become, Conditional, Taggable): for dep in self._dep_chain: dep.set_loader(loader) - def _get_parent_attribute(self, attr): + def _get_parent_attribute(self, attr, extend=False): ''' Generic logic to get the attribute or parent attribute for a block value. ''' value = self._attributes[attr] - if not value: - if self._parent_block: - value = getattr(self._parent_block, attr) - elif self._role: - value = getattr(self._role, attr) - if not value and len(self._dep_chain): - reverse_dep_chain = self._dep_chain[:] - reverse_dep_chain.reverse() - for dep in reverse_dep_chain: - value = getattr(dep, attr) - if value: - break + if self._parent_block and (not value or extend): + parent_value = getattr(self._parent_block, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value + if self._task_include and (not value or extend): + parent_value = getattr(self._task_include, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value + if self._role and (not value or extend): + parent_value = getattr(self._role, attr) + if len(self._dep_chain) and (not value or extend): + reverse_dep_chain = self._dep_chain[:] + reverse_dep_chain.reverse() + for dep in reverse_dep_chain: + dep_value = getattr(dep, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value + + if value and not extend: + break + if self._play and (not value or extend): + parent_value = getattr(self._play, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value return value diff --git a/v2/ansible/playbook/helpers.py b/v2/ansible/playbook/helpers.py index 92f1c64c83..302e14a6e0 100644 --- a/v2/ansible/playbook/helpers.py +++ b/v2/ansible/playbook/helpers.py @@ -26,7 +26,7 @@ from ansible.errors import AnsibleParserError from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleSequence -def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): +def load_list_of_blocks(ds, play, parent_block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): ''' Given a list of mixed task/block data (parsed from YAML), return a list of Block() objects, where implicit blocks @@ -43,6 +43,7 @@ def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, use for block in ds: b = Block.load( block, + play=play, parent_block=parent_block, role=role, task_include=task_include, @@ -55,7 +56,7 @@ def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, use return block_list -def load_list_of_tasks(ds, block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): +def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None): ''' Given a list of task datastructures (parsed from YAML), return a list of Task() or TaskInclude() objects. @@ -76,6 +77,7 @@ def load_list_of_tasks(ds, block=None, role=None, task_include=None, use_handler if 'block' in task: t = Block.load( task, + play=play, parent_block=block, role=role, task_include=task_include, diff --git a/v2/ansible/playbook/play.py b/v2/ansible/playbook/play.py index 457f238109..b99c01fdf7 100644 --- a/v2/ansible/playbook/play.py +++ b/v2/ansible/playbook/play.py @@ -145,28 +145,28 @@ class Play(Base, Taggable, Become): Loads a list of blocks from a list which may be mixed tasks/blocks. Bare tasks outside of a block are given an implicit block. ''' - return load_list_of_blocks(ds, variable_manager=self._variable_manager, loader=self._loader) + return load_list_of_blocks(ds=ds, play=self, variable_manager=self._variable_manager, loader=self._loader) def _load_pre_tasks(self, attr, ds): ''' Loads a list of blocks from a list which may be mixed tasks/blocks. Bare tasks outside of a block are given an implicit block. ''' - return load_list_of_blocks(ds, variable_manager=self._variable_manager, loader=self._loader) + return load_list_of_blocks(ds=ds, play=self, variable_manager=self._variable_manager, loader=self._loader) def _load_post_tasks(self, attr, ds): ''' Loads a list of blocks from a list which may be mixed tasks/blocks. Bare tasks outside of a block are given an implicit block. ''' - return load_list_of_blocks(ds, variable_manager=self._variable_manager, loader=self._loader) + return load_list_of_blocks(ds=ds, play=self, variable_manager=self._variable_manager, loader=self._loader) def _load_handlers(self, attr, ds): ''' Loads a list of blocks from a list which may be mixed handlers/blocks. Bare handlers outside of a block are given an implicit block. ''' - return load_list_of_blocks(ds, use_handlers=True, variable_manager=self._variable_manager, loader=self._loader) + return load_list_of_blocks(ds=ds, play=self, use_handlers=True, variable_manager=self._variable_manager, loader=self._loader) def _load_roles(self, attr, ds): ''' @@ -196,7 +196,7 @@ class Play(Base, Taggable, Become): if len(self.roles) > 0: for r in self.roles: - block_list.extend(r.compile()) + block_list.extend(r.compile(play=self)) return block_list diff --git a/v2/ansible/playbook/role/__init__.py b/v2/ansible/playbook/role/__init__.py index bc4d4262eb..33935d197f 100644 --- a/v2/ansible/playbook/role/__init__.py +++ b/v2/ansible/playbook/role/__init__.py @@ -79,6 +79,7 @@ class Role(Base, Become, Conditional, Taggable): self._loader = None self._metadata = None + self._play = None self._parents = [] self._dependencies = [] self._task_blocks = [] @@ -163,11 +164,11 @@ class Role(Base, Become, Conditional, Taggable): task_data = self._load_role_yaml('tasks') if task_data: - self._task_blocks = load_list_of_blocks(task_data, role=self, loader=self._loader) + self._task_blocks = load_list_of_blocks(task_data, play=None, role=self, loader=self._loader) handler_data = self._load_role_yaml('handlers') if handler_data: - self._handler_blocks = load_list_of_blocks(handler_data, role=self, loader=self._loader) + self._handler_blocks = load_list_of_blocks(handler_data, play=None, role=self, loader=self._loader) # vars and default vars are regular dictionaries self._role_vars = self._load_role_yaml('vars') @@ -293,7 +294,7 @@ class Role(Base, Become, Conditional, Taggable): return self._had_task_run and self._completed - def compile(self, dep_chain=[]): + def compile(self, play, dep_chain=[]): ''' Returns the task list for this role, which is created by first recursively compiling the tasks for all direct dependencies, and @@ -311,10 +312,11 @@ class Role(Base, Become, Conditional, Taggable): deps = self.get_direct_dependencies() for dep in deps: - dep_blocks = dep.compile(dep_chain=new_dep_chain) + dep_blocks = dep.compile(play=play, dep_chain=new_dep_chain) for dep_block in dep_blocks: new_dep_block = dep_block.copy() new_dep_block._dep_chain = new_dep_chain + new_dep_block._play = play block_list.append(new_dep_block) block_list.extend(self._task_blocks) diff --git a/v2/ansible/playbook/taggable.py b/v2/ansible/playbook/taggable.py index f721cd195f..3622dc34b2 100644 --- a/v2/ansible/playbook/taggable.py +++ b/v2/ansible/playbook/taggable.py @@ -26,7 +26,7 @@ from ansible.template import Templar class Taggable: untagged = set(['untagged']) - _tags = FieldAttribute(isa='list', default=[]) + _tags = FieldAttribute(isa='list', default=None) def __init__(self): super(Taggable, self).__init__() @@ -44,9 +44,11 @@ class Taggable: Override for the 'tags' getattr fetcher, used from Base. ''' tags = self._attributes['tags'] + if tags is None: + tags = [] if hasattr(self, '_get_parent_attribute'): - tags.extend(self._get_parent_attribute('tags')) - return list(set(tags)) + tags = self._get_parent_attribute('tags', extend=True) + return tags def evaluate_tags(self, only_tags, skip_tags, all_vars): ''' this checks if the current item should be executed depending on tag options ''' diff --git a/v2/ansible/playbook/task.py b/v2/ansible/playbook/task.py index bdffc13eb8..06f7239d1b 100644 --- a/v2/ansible/playbook/task.py +++ b/v2/ansible/playbook/task.py @@ -205,16 +205,6 @@ class Task(Base, Conditional, Taggable, Become): del all_vars['when'] return all_vars - # no longer used, as blocks are the lowest level of compilation now - #def compile(self): - # ''' - # For tasks, this is just a dummy method returning an array - # with 'self' in it, so we don't have to care about task types - # further up the chain. - # ''' - # - # return [self] - def copy(self, exclude_block=False): new_me = super(Task, self).copy() @@ -299,12 +289,22 @@ class Task(Base, Conditional, Taggable, Become): if self._task_include: self._task_include.set_loader(loader) - def _get_parent_attribute(self, attr): + def _get_parent_attribute(self, attr, extend=False): ''' Generic logic to get the attribute or parent attribute for a task value. ''' value = self._attributes[attr] - if not value and self._block: - value = getattr(self._block, attr) + if self._block and (not value or extend): + parent_value = getattr(self._block, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value + if self._task_include and (not value or extend): + parent_value = getattr(self._task_include, attr) + if extend: + value = self._extend_value(value, parent_value) + else: + value = parent_value return value diff --git a/v2/ansible/plugins/strategies/__init__.py b/v2/ansible/plugins/strategies/__init__.py index d01360463b..238c6222a8 100644 --- a/v2/ansible/plugins/strategies/__init__.py +++ b/v2/ansible/plugins/strategies/__init__.py @@ -308,6 +308,7 @@ class StrategyBase: is_handler = isinstance(included_file._task, Handler) block_list = load_list_of_blocks( data, + play=included_file._task._block._play, parent_block=included_file._task._block, task_include=included_file._task, role=included_file._task._role, diff --git a/v2/samples/test_tags.yml b/v2/samples/test_tags.yml index c94b88e0a0..d352cf9bfb 100644 --- a/v2/samples/test_tags.yml +++ b/v2/samples/test_tags.yml @@ -1,10 +1,17 @@ - hosts: localhost gather_facts: no + vars: + a: "tags" + tags: + - play tasks: - block: - debug: msg="this is the tagged block" tags: - block + - include: include.yml + tags: + - include - block: - debug: msg="tagged debug from second block" tags: