Don't use getattr in _get_parent_attribute to avoid recursion issues (#33595)
* Don't use getattr in _get_parent_attribute to avoid recursion issues Fixes #23609 * Move extend/prepend to field attribute Also removes _get_attr* methods that were basically just calling _get_parent_attribute because it needed to set those params. Also modifies _get_parent_attribute() to pull those values from the FieldAttributes instead of using the ones passed into the function. * Better fixes for _get_parent_attribute
This commit is contained in:
parent
ab5dbca47e
commit
ebf971f931
7 changed files with 53 additions and 54 deletions
|
@ -24,8 +24,22 @@ from copy import deepcopy
|
||||||
|
|
||||||
class Attribute:
|
class Attribute:
|
||||||
|
|
||||||
def __init__(self, isa=None, private=False, default=None, required=False, listof=None, priority=0, class_type=None, always_post_validate=False,
|
def __init__(
|
||||||
inherit=True, alias=None):
|
self,
|
||||||
|
isa=None,
|
||||||
|
private=False,
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
listof=None,
|
||||||
|
priority=0,
|
||||||
|
class_type=None,
|
||||||
|
always_post_validate=False,
|
||||||
|
inherit=True,
|
||||||
|
alias=None,
|
||||||
|
extend=False,
|
||||||
|
prepend=False,
|
||||||
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
:class:`Attribute` specifies constraints for attributes of objects which
|
:class:`Attribute` specifies constraints for attributes of objects which
|
||||||
derive from playbook data. The attributes of the object are basically
|
derive from playbook data. The attributes of the object are basically
|
||||||
|
@ -67,6 +81,8 @@ class Attribute:
|
||||||
self.always_post_validate = always_post_validate
|
self.always_post_validate = always_post_validate
|
||||||
self.inherit = inherit
|
self.inherit = inherit
|
||||||
self.alias = alias
|
self.alias = alias
|
||||||
|
self.extend = extend
|
||||||
|
self.prepend = prepend
|
||||||
|
|
||||||
if default is not None and self.isa in ('list', 'dict', 'set'):
|
if default is not None and self.isa in ('list', 'dict', 'set'):
|
||||||
self.default = deepcopy(default)
|
self.default = deepcopy(default)
|
||||||
|
|
|
@ -48,12 +48,13 @@ def _generic_g_method(prop_name, self):
|
||||||
|
|
||||||
def _generic_g_parent(prop_name, self):
|
def _generic_g_parent(prop_name, self):
|
||||||
try:
|
try:
|
||||||
value = self._attributes[prop_name]
|
if self._squashed or self._finalized:
|
||||||
if value is None and not self._squashed and not self._finalized:
|
value = self._attributes[prop_name]
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
value = self._get_parent_attribute(prop_name)
|
value = self._get_parent_attribute(prop_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
value = self._attributes[prop_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
|
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ class Base(with_metaclass(BaseMeta, object)):
|
||||||
_vars = FieldAttribute(isa='dict', priority=100, inherit=False)
|
_vars = FieldAttribute(isa='dict', priority=100, inherit=False)
|
||||||
|
|
||||||
# flags and misc. settings
|
# flags and misc. settings
|
||||||
_environment = FieldAttribute(isa='list')
|
_environment = FieldAttribute(isa='list', extend=True, prepend=True)
|
||||||
_no_log = FieldAttribute(isa='bool')
|
_no_log = FieldAttribute(isa='bool')
|
||||||
_always_run = FieldAttribute(isa='bool')
|
_always_run = FieldAttribute(isa='bool')
|
||||||
_run_once = FieldAttribute(isa='bool')
|
_run_once = FieldAttribute(isa='bool')
|
||||||
|
|
|
@ -278,22 +278,22 @@ class Block(Base, Become, Conditional, Taggable):
|
||||||
for dep in dep_chain:
|
for dep in dep_chain:
|
||||||
dep.set_loader(loader)
|
dep.set_loader(loader)
|
||||||
|
|
||||||
def _get_attr_environment(self):
|
|
||||||
return self._get_parent_attribute('environment', extend=True, prepend=True)
|
|
||||||
|
|
||||||
def _get_parent_attribute(self, attr, extend=False, prepend=False):
|
def _get_parent_attribute(self, attr, extend=False, prepend=False):
|
||||||
'''
|
'''
|
||||||
Generic logic to get the attribute or parent attribute for a block value.
|
Generic logic to get the attribute or parent attribute for a block value.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
value = None
|
extend = self._valid_attrs[attr].extend
|
||||||
|
prepend = self._valid_attrs[attr].prepend
|
||||||
try:
|
try:
|
||||||
value = self._attributes[attr]
|
value = self._attributes[attr]
|
||||||
|
|
||||||
if self._parent and (value is None or extend):
|
if self._parent and (value is None or extend):
|
||||||
try:
|
try:
|
||||||
if attr != 'when' or getattr(self._parent, 'statically_loaded', True):
|
if getattr(self._parent, 'statically_loaded', True):
|
||||||
parent_value = getattr(self._parent, attr, None)
|
if hasattr(self._parent, '_get_parent_attribute'):
|
||||||
|
parent_value = self._parent._get_parent_attribute(attr)
|
||||||
|
else:
|
||||||
|
parent_value = self._parent._attributes.get(attr, None)
|
||||||
if extend:
|
if extend:
|
||||||
value = self._extend_value(value, parent_value, prepend)
|
value = self._extend_value(value, parent_value, prepend)
|
||||||
else:
|
else:
|
||||||
|
@ -302,7 +302,10 @@ class Block(Base, Become, Conditional, Taggable):
|
||||||
pass
|
pass
|
||||||
if self._role and (value is None or extend):
|
if self._role and (value is None or extend):
|
||||||
try:
|
try:
|
||||||
parent_value = getattr(self._role, attr, None)
|
if hasattr(self._role, '_get_parent_attribute'):
|
||||||
|
parent_value = self._role.get_parent_attribute(attr)
|
||||||
|
else:
|
||||||
|
parent_value = self._role._attributes.get(attr, None)
|
||||||
if extend:
|
if extend:
|
||||||
value = self._extend_value(value, parent_value, prepend)
|
value = self._extend_value(value, parent_value, prepend)
|
||||||
else:
|
else:
|
||||||
|
@ -312,7 +315,10 @@ class Block(Base, Become, Conditional, Taggable):
|
||||||
if dep_chain and (value is None or extend):
|
if dep_chain and (value is None or extend):
|
||||||
dep_chain.reverse()
|
dep_chain.reverse()
|
||||||
for dep in dep_chain:
|
for dep in dep_chain:
|
||||||
dep_value = getattr(dep, attr, None)
|
if hasattr(dep, '_get_parent_attribute'):
|
||||||
|
dep_value = dep._get_parent_attribute(attr)
|
||||||
|
else:
|
||||||
|
dep_value = dep._attributes.get(attr, None)
|
||||||
if extend:
|
if extend:
|
||||||
value = self._extend_value(value, dep_value, prepend)
|
value = self._extend_value(value, dep_value, prepend)
|
||||||
else:
|
else:
|
||||||
|
@ -324,11 +330,12 @@ class Block(Base, Become, Conditional, Taggable):
|
||||||
pass
|
pass
|
||||||
if self._play and (value is None or extend):
|
if self._play and (value is None or extend):
|
||||||
try:
|
try:
|
||||||
parent_value = getattr(self._play, attr, None)
|
play_value = self._play._attributes.get(attr, None)
|
||||||
if extend:
|
if play_value is not None:
|
||||||
value = self._extend_value(value, parent_value, prepend)
|
if extend:
|
||||||
else:
|
value = self._extend_value(value, play_value, prepend)
|
||||||
value = parent_value
|
else:
|
||||||
|
value = play_value
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -49,7 +49,7 @@ class Conditional:
|
||||||
to be run conditionally when a condition is met or skipped.
|
to be run conditionally when a condition is met or skipped.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
_when = FieldAttribute(isa='list', default=[])
|
_when = FieldAttribute(isa='list', default=[], extend=True, prepend=True)
|
||||||
|
|
||||||
def __init__(self, loader=None):
|
def __init__(self, loader=None):
|
||||||
# when used directly, this class needs a loader, but we want to
|
# when used directly, this class needs a loader, but we want to
|
||||||
|
@ -66,17 +66,6 @@ class Conditional:
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
setattr(self, name, [value])
|
setattr(self, name, [value])
|
||||||
|
|
||||||
def _get_attr_when(self):
|
|
||||||
'''
|
|
||||||
Override for the 'tags' getattr fetcher, used from Base.
|
|
||||||
'''
|
|
||||||
when = self._attributes['when']
|
|
||||||
if when is None:
|
|
||||||
when = []
|
|
||||||
if hasattr(self, '_get_parent_attribute'):
|
|
||||||
when = self._get_parent_attribute('when', extend=True, prepend=True)
|
|
||||||
return when
|
|
||||||
|
|
||||||
def extract_defined_undefined(self, conditional):
|
def extract_defined_undefined(self, conditional):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ from ansible.template import Templar
|
||||||
class Taggable:
|
class Taggable:
|
||||||
|
|
||||||
untagged = frozenset(['untagged'])
|
untagged = frozenset(['untagged'])
|
||||||
_tags = FieldAttribute(isa='list', default=[], listof=(string_types, int))
|
_tags = FieldAttribute(isa='list', default=[], listof=(string_types, int), extend=True)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Taggable, self).__init__()
|
super(Taggable, self).__init__()
|
||||||
|
@ -47,15 +47,6 @@ class Taggable:
|
||||||
else:
|
else:
|
||||||
raise AnsibleError('tags must be specified as a list', obj=ds)
|
raise AnsibleError('tags must be specified as a list', obj=ds)
|
||||||
|
|
||||||
def _get_attr_tags(self):
|
|
||||||
'''
|
|
||||||
Override for the 'tags' getattr fetcher, used from Base, allow some classes to not give their tags to their 'children'
|
|
||||||
'''
|
|
||||||
tags = self._attributes.get('tags', [])
|
|
||||||
if hasattr(self, '_get_parent_attribute'):
|
|
||||||
tags = self._get_parent_attribute('tags', extend=True)
|
|
||||||
return tags
|
|
||||||
|
|
||||||
def evaluate_tags(self, only_tags, skip_tags, all_vars):
|
def evaluate_tags(self, only_tags, skip_tags, all_vars):
|
||||||
''' this checks if the current item should be executed depending on tag options '''
|
''' this checks if the current item should be executed depending on tag options '''
|
||||||
|
|
||||||
|
|
|
@ -414,16 +414,17 @@ class Task(Base, Conditional, Taggable, Become):
|
||||||
Generic logic to get the attribute or parent attribute for a task value.
|
Generic logic to get the attribute or parent attribute for a task value.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
value = None
|
extend = self._valid_attrs[attr].extend
|
||||||
|
prepend = self._valid_attrs[attr].prepend
|
||||||
try:
|
try:
|
||||||
value = self._attributes[attr]
|
value = self._attributes[attr]
|
||||||
if self._parent and (value is None or extend):
|
if self._parent and (value is None or extend):
|
||||||
if attr != 'when' or getattr(self._parent, 'statically_loaded', True):
|
if getattr(self._parent, 'statically_loaded', True):
|
||||||
# vars are always inheritable, other attributes might not be for the partent but still should be for other ancestors
|
# vars are always inheritable, other attributes might not be for the partent but still should be for other ancestors
|
||||||
if attr != 'vars' and not getattr(self._parent, '_inheritable', True) and hasattr(self._parent, '_get_parent_attribute'):
|
if attr != 'vars' and getattr(self._parent, '_inheritable', True) and hasattr(self._parent, '_get_parent_attribute'):
|
||||||
parent_value = self._parent._get_parent_attribute(attr, extend=extend, prepend=prepend)
|
parent_value = self._parent._get_parent_attribute(attr)
|
||||||
else:
|
else:
|
||||||
parent_value = getattr(self._parent, attr, None)
|
parent_value = self._parent._attributes.get(attr, None)
|
||||||
|
|
||||||
if extend:
|
if extend:
|
||||||
value = self._extend_value(value, parent_value, prepend)
|
value = self._extend_value(value, parent_value, prepend)
|
||||||
|
@ -442,12 +443,6 @@ class Task(Base, Conditional, Taggable, Become):
|
||||||
value = C.ANY_ERRORS_FATAL
|
value = C.ANY_ERRORS_FATAL
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _get_attr_environment(self):
|
|
||||||
'''
|
|
||||||
Override for the 'tags' getattr fetcher, used from Base.
|
|
||||||
'''
|
|
||||||
return self._get_parent_attribute('environment', extend=True, prepend=True)
|
|
||||||
|
|
||||||
def get_dep_chain(self):
|
def get_dep_chain(self):
|
||||||
if self._parent:
|
if self._parent:
|
||||||
return self._parent.get_dep_chain()
|
return self._parent.get_dep_chain()
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
include_tasks: prepare.yml
|
include_tasks: prepare.yml
|
||||||
|
|
||||||
- name: Test in check-mode
|
- name: Test in check-mode
|
||||||
include_tasks: tests.yml
|
import_tasks: tests.yml
|
||||||
vars:
|
vars:
|
||||||
in_check_mode: yes
|
in_check_mode: yes
|
||||||
check_mode: yes
|
check_mode: yes
|
||||||
|
|
Loading…
Reference in a new issue