community.general/lib/ansible/playbook/task_include.py
Matt Martz 8c08d03989
Fieldattribute inheritance with defaults (#50891)
* Add tests for check_mode at play and task level

These test inheritance of check_mode from the various levels (command
line, as a play attribute and as a task attribute) so they will be
useful for checking that the change to fieldattribute inheritance with
defaults works

* Add a sentinel object

The Sentinel object can be used in place of None when we need to mark an
entry as being special (usually used to mark something as not having
been set)

* Start of using a Sentinel object instead of None.

* Handle edge cases around use of Sentinel

* _get_parent_attribute needs to deal in Sentinel not None

* No need to special case any_errors_fatal in task.py any longer

* Handle more edge cases around Sentinel

* Use Sentinel instead of None in TaskInclude

* Update code to clarify the vars we are copying are class attrs

* Add changelog fragment

* Use a default of Sentinel for delegate_to, this also allows 'delegate_to: ~' now to unset inherited delegate_to

* Explain Sentinel stripping in _extend_value

* Fix ModuleArgsParser tests to compare with Sentinel

* Fixes for tasks inside of roles inheriting from play

* Remove incorrect note. ci_complete

* Remove commented code
2019-01-23 11:40:07 -06:00

143 lines
5.5 KiB
Python

# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import ansible.constants as C
from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.block import Block
from ansible.playbook.task import Task
from ansible.utils.display import Display
from ansible.utils.sentinel import Sentinel
__all__ = ['TaskInclude']
display = Display()
class TaskInclude(Task):
"""
A task include is derived from a regular task to handle the special
circumstances related to the `- include: ...` task.
"""
BASE = frozenset(('file', '_raw_params')) # directly assigned
OTHER_ARGS = frozenset(('apply',)) # assigned to matching property
VALID_ARGS = BASE.union(OTHER_ARGS) # all valid args
VALID_INCLUDE_KEYWORDS = frozenset(('action', 'args', 'debugger', 'ignore_errors', 'loop', 'loop_control',
'loop_with', 'name', 'no_log', 'register', 'run_once', 'tags', 'vars',
'when'))
# =================================================================================
# ATTRIBUTES
_static = FieldAttribute(isa='bool', default=None)
def __init__(self, block=None, role=None, task_include=None):
super(TaskInclude, self).__init__(block=block, role=role, task_include=task_include)
self.statically_loaded = False
@staticmethod
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
ti = TaskInclude(block=block, role=role, task_include=task_include)
task = ti.load_data(data, variable_manager=variable_manager, loader=loader)
# Validate options
my_arg_names = frozenset(task.args.keys())
# validate bad args, otherwise we silently ignore
bad_opts = my_arg_names.difference(TaskInclude.VALID_ARGS)
if bad_opts and task.action in ('include_tasks', 'import_tasks'):
raise AnsibleParserError('Invalid options for %s: %s' % (task.action, ','.join(list(bad_opts))), obj=data)
if not task.args.get('_raw_params'):
task.args['_raw_params'] = task.args.pop('file')
apply_attrs = task.args.get('apply', {})
if apply_attrs and task.action != 'include_tasks':
raise AnsibleParserError('Invalid options for %s: apply' % task.action, obj=data)
elif not isinstance(apply_attrs, dict):
raise AnsibleParserError('Expected a dict for apply but got %s instead' % type(apply_attrs), obj=data)
return task
def preprocess_data(self, ds):
ds = super(TaskInclude, self).preprocess_data(ds)
diff = set(ds.keys()).difference(TaskInclude.VALID_INCLUDE_KEYWORDS)
for k in diff:
# 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 C.INVALID_TASK_ATTRIBUTE_FAILED:
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (k, self.__class__.__name__), obj=ds)
else:
display.warning("Ignoring invalid attribute: %s" % k)
return ds
def copy(self, exclude_parent=False, exclude_tasks=False):
new_me = super(TaskInclude, self).copy(exclude_parent=exclude_parent, exclude_tasks=exclude_tasks)
new_me.statically_loaded = self.statically_loaded
return new_me
def get_vars(self):
'''
We override the parent Task() classes get_vars here because
we need to include the args of the include into the vars as
they are params to the included tasks. But ONLY for 'include'
'''
if self.action != 'include':
all_vars = super(TaskInclude, self).get_vars()
else:
all_vars = dict()
if self._parent:
all_vars.update(self._parent.get_vars())
all_vars.update(self.vars)
all_vars.update(self.args)
if 'tags' in all_vars:
del all_vars['tags']
if 'when' in all_vars:
del all_vars['when']
return all_vars
def build_parent_block(self):
'''
This method is used to create the parent block for the included tasks
when ``apply`` is specified
'''
apply_attrs = self.args.pop('apply', {})
if apply_attrs:
apply_attrs['block'] = []
p_block = Block.load(
apply_attrs,
play=self._parent._play,
task_include=self,
role=self._role,
variable_manager=self._variable_manager,
loader=self._loader,
)
else:
p_block = self
return p_block