|
|
|
@ -24,10 +24,11 @@ import itertools
|
|
|
|
|
import operator
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
from copy import deepcopy
|
|
|
|
|
from functools import partial
|
|
|
|
|
from inspect import getmembers
|
|
|
|
|
|
|
|
|
|
from ansible.compat.six import iteritems, string_types
|
|
|
|
|
from ansible.compat.six import iteritems, string_types, with_metaclass
|
|
|
|
|
|
|
|
|
|
from jinja2.exceptions import UndefinedError
|
|
|
|
|
|
|
|
|
@ -44,10 +45,64 @@ except ImportError:
|
|
|
|
|
from ansible.utils.display import Display
|
|
|
|
|
display = Display()
|
|
|
|
|
|
|
|
|
|
BASE_ATTRIBUTES = {}
|
|
|
|
|
|
|
|
|
|
def _generic_g(prop_name, self):
|
|
|
|
|
method = "_get_attr_%s" % prop_name
|
|
|
|
|
try:
|
|
|
|
|
value = getattr(self, method)()
|
|
|
|
|
except AttributeError:
|
|
|
|
|
try:
|
|
|
|
|
value = self._attributes[prop_name]
|
|
|
|
|
if value is None and not self._finalized:
|
|
|
|
|
try:
|
|
|
|
|
value = self._get_parent_attribute(prop_name)
|
|
|
|
|
except AttributeError:
|
|
|
|
|
pass
|
|
|
|
|
except KeyError:
|
|
|
|
|
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, prop_name))
|
|
|
|
|
|
|
|
|
|
class Base:
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def _generic_s(prop_name, self, value):
|
|
|
|
|
self._attributes[prop_name] = value
|
|
|
|
|
|
|
|
|
|
def _generic_d(prop_name, self):
|
|
|
|
|
del self._attributes[prop_name]
|
|
|
|
|
|
|
|
|
|
class BaseMeta(type):
|
|
|
|
|
|
|
|
|
|
def __new__(cls, name, parents, dct):
|
|
|
|
|
def _create_attrs(src_dict, dst_dict):
|
|
|
|
|
keys = list(src_dict.keys())
|
|
|
|
|
for attr_name in keys:
|
|
|
|
|
value = src_dict[attr_name]
|
|
|
|
|
if isinstance(value, Attribute):
|
|
|
|
|
if attr_name.startswith('_'):
|
|
|
|
|
attr_name = attr_name[1:]
|
|
|
|
|
|
|
|
|
|
getter = partial(_generic_g, attr_name)
|
|
|
|
|
setter = partial(_generic_s, attr_name)
|
|
|
|
|
deleter = partial(_generic_d, attr_name)
|
|
|
|
|
|
|
|
|
|
dst_dict[attr_name] = property(getter, setter, deleter)
|
|
|
|
|
dst_dict['_valid_attrs'][attr_name] = value
|
|
|
|
|
dst_dict['_attributes'][attr_name] = value.default
|
|
|
|
|
|
|
|
|
|
def _process_parents(parents, dst_dict):
|
|
|
|
|
for parent in parents:
|
|
|
|
|
if hasattr(parent, '__dict__'):
|
|
|
|
|
_create_attrs(parent.__dict__, dst_dict)
|
|
|
|
|
_process_parents(parent.__bases__, dst_dict)
|
|
|
|
|
|
|
|
|
|
dct['_attributes'] = dict()
|
|
|
|
|
dct['_valid_attrs'] = dict()
|
|
|
|
|
|
|
|
|
|
_create_attrs(dct, dct)
|
|
|
|
|
_process_parents(parents, dct)
|
|
|
|
|
|
|
|
|
|
return super(BaseMeta, cls).__new__(cls, name, parents, dct)
|
|
|
|
|
|
|
|
|
|
class Base(with_metaclass(BaseMeta, object)):
|
|
|
|
|
|
|
|
|
|
# connection/transport
|
|
|
|
|
_connection = FieldAttribute(isa='string')
|
|
|
|
@ -85,85 +140,15 @@ class Base:
|
|
|
|
|
# every object gets a random uuid:
|
|
|
|
|
self._uuid = uuid.uuid4()
|
|
|
|
|
|
|
|
|
|
# and initialize the base attributes
|
|
|
|
|
self._initialize_base_attributes()
|
|
|
|
|
|
|
|
|
|
self._cached_parent_attrs = dict()
|
|
|
|
|
# initialize the default field attribute values
|
|
|
|
|
#self._attributes = dict()
|
|
|
|
|
#for (name, attr) in iteritems(self._valid_attrs):
|
|
|
|
|
# self._attributes[name] = attr.default
|
|
|
|
|
self._attributes = self._attributes.copy()
|
|
|
|
|
|
|
|
|
|
# and init vars, avoid using defaults in field declaration as it lives across plays
|
|
|
|
|
self.vars = dict()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# The following three functions are used to programatically define data
|
|
|
|
|
# descriptors (aka properties) for the Attributes of all of the playbook
|
|
|
|
|
# objects (tasks, blocks, plays, etc).
|
|
|
|
|
#
|
|
|
|
|
# The function signature is a little strange because of how we define
|
|
|
|
|
# them. We use partial to give each method the name of the Attribute that
|
|
|
|
|
# it is for. Since partial prefills the positional arguments at the
|
|
|
|
|
# beginning of the function we end up with the first positional argument
|
|
|
|
|
# being allocated to the name instead of to the class instance (self) as
|
|
|
|
|
# normal. To deal with that we make the property name field the first
|
|
|
|
|
# positional argument and self the second arg.
|
|
|
|
|
#
|
|
|
|
|
# Because these methods are defined inside of the class, they get bound to
|
|
|
|
|
# the instance when the object is created. After we run partial on them
|
|
|
|
|
# and put the result back into the class as a property, they get bound
|
|
|
|
|
# a second time. This leads to self being placed in the arguments twice.
|
|
|
|
|
# To work around that, we mark the functions as @staticmethod so that the
|
|
|
|
|
# first binding to the instance doesn't happen.
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _generic_g(prop_name, self):
|
|
|
|
|
method = "_get_attr_%s" % prop_name
|
|
|
|
|
try:
|
|
|
|
|
value = getattr(self, method)()
|
|
|
|
|
except AttributeError:
|
|
|
|
|
try:
|
|
|
|
|
value = self._attributes[prop_name]
|
|
|
|
|
if value is None and not self._finalized:
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _generic_s(prop_name, self, value):
|
|
|
|
|
self._attributes[prop_name] = value
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _generic_d(prop_name, self):
|
|
|
|
|
del self._attributes[prop_name]
|
|
|
|
|
|
|
|
|
|
def _get_base_attributes(self):
|
|
|
|
|
'''
|
|
|
|
|
Returns the list of attributes for this class (or any subclass thereof).
|
|
|
|
|
If the attribute name starts with an underscore, it is removed
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# check cache before retrieving attributes
|
|
|
|
|
if self.__class__.__name__ in BASE_ATTRIBUTES:
|
|
|
|
|
return BASE_ATTRIBUTES[self.__class__.__name__]
|
|
|
|
|
|
|
|
|
|
# Cache init
|
|
|
|
|
base_attributes = dict()
|
|
|
|
|
for (name, value) in getmembers(self.__class__):
|
|
|
|
|
if isinstance(value, Attribute):
|
|
|
|
|
if name.startswith('_'):
|
|
|
|
|
name = name[1:]
|
|
|
|
|
base_attributes[name] = value
|
|
|
|
|
BASE_ATTRIBUTES[self.__class__.__name__] = base_attributes
|
|
|
|
|
return base_attributes
|
|
|
|
|
|
|
|
|
|
def dump_me(self, depth=0):
|
|
|
|
|
if depth == 0:
|
|
|
|
|
print("DUMPING OBJECT ------------------------------------------------------")
|
|
|
|
@ -178,23 +163,6 @@ class Base:
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
for (name, value) in self._get_base_attributes().items():
|
|
|
|
|
getter = partial(self._generic_g, name)
|
|
|
|
|
setter = partial(self._generic_s, name)
|
|
|
|
|
deleter = partial(self._generic_d, name)
|
|
|
|
|
|
|
|
|
|
# Place the property into the class so that cls.name is the
|
|
|
|
|
# property functions.
|
|
|
|
|
setattr(Base, name, property(getter, setter, deleter))
|
|
|
|
|
|
|
|
|
|
# Place the value into the instance so that the property can
|
|
|
|
|
# process and hold that value.
|
|
|
|
|
setattr(self, name, value.default)
|
|
|
|
|
|
|
|
|
|
def preprocess_data(self, ds):
|
|
|
|
|
''' infrequently used method to do some pre-processing of legacy terms '''
|
|
|
|
|
|
|
|
|
@ -230,8 +198,7 @@ class Base:
|
|
|
|
|
|
|
|
|
|
# Walk all attributes in the class. We sort them based on their priority
|
|
|
|
|
# so that certain fields can be loaded before others, if they are dependent.
|
|
|
|
|
base_attributes = self._get_base_attributes()
|
|
|
|
|
for name, attr in sorted(base_attributes.items(), key=operator.itemgetter(1)):
|
|
|
|
|
for name, attr in sorted(iteritems(self._valid_attrs), key=operator.itemgetter(1)):
|
|
|
|
|
# copy the value over unless a _load_field method is defined
|
|
|
|
|
if name in ds:
|
|
|
|
|
method = getattr(self, '_load_%s' % name, None)
|
|
|
|
@ -264,7 +231,7 @@ class Base:
|
|
|
|
|
not map to attributes for this object.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
valid_attrs = frozenset(name for name in self._get_base_attributes())
|
|
|
|
|
valid_attrs = frozenset(self._valid_attrs.keys())
|
|
|
|
|
for key in ds:
|
|
|
|
|
if key not in valid_attrs:
|
|
|
|
|
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (key, self.__class__.__name__), obj=ds)
|
|
|
|
@ -274,7 +241,7 @@ class Base:
|
|
|
|
|
|
|
|
|
|
if not self._validated:
|
|
|
|
|
# walk all fields in the object
|
|
|
|
|
for (name, attribute) in iteritems(self._get_base_attributes()):
|
|
|
|
|
for (name, attribute) in iteritems(self._valid_attrs):
|
|
|
|
|
|
|
|
|
|
# run validator only if present
|
|
|
|
|
method = getattr(self, '_validate_%s' % name, None)
|
|
|
|
@ -299,7 +266,7 @@ class Base:
|
|
|
|
|
|
|
|
|
|
new_me = self.__class__()
|
|
|
|
|
|
|
|
|
|
for name in self._get_base_attributes():
|
|
|
|
|
for name in self._valid_attrs.keys():
|
|
|
|
|
attr_val = getattr(self, name)
|
|
|
|
|
if isinstance(attr_val, collections.Sequence):
|
|
|
|
|
setattr(new_me, name, attr_val[:])
|
|
|
|
@ -330,7 +297,7 @@ class Base:
|
|
|
|
|
# save the omit value for later checking
|
|
|
|
|
omit_value = templar._available_variables.get('omit')
|
|
|
|
|
|
|
|
|
|
for (name, attribute) in iteritems(self._get_base_attributes()):
|
|
|
|
|
for (name, attribute) in iteritems(self._valid_attrs):
|
|
|
|
|
|
|
|
|
|
if getattr(self, name) is None:
|
|
|
|
|
if not attribute.required:
|
|
|
|
@ -432,44 +399,6 @@ class Base:
|
|
|
|
|
|
|
|
|
|
self._finalized = True
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
'''
|
|
|
|
|
Serializes the object derived from the base object into
|
|
|
|
|
a dictionary of values. This only serializes the field
|
|
|
|
|
attributes for the object, so this may need to be overridden
|
|
|
|
|
for any classes which wish to add additional items not stored
|
|
|
|
|
as field attributes.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
repr = dict()
|
|
|
|
|
|
|
|
|
|
for name in self._get_base_attributes():
|
|
|
|
|
repr[name] = getattr(self, name)
|
|
|
|
|
|
|
|
|
|
# serialize the uuid field
|
|
|
|
|
repr['uuid'] = getattr(self, '_uuid')
|
|
|
|
|
|
|
|
|
|
return repr
|
|
|
|
|
|
|
|
|
|
def deserialize(self, data):
|
|
|
|
|
'''
|
|
|
|
|
Given a dictionary of values, load up the field attributes for
|
|
|
|
|
this object. As with serialize(), if there are any non-field
|
|
|
|
|
attribute data members, this method will need to be overridden
|
|
|
|
|
and extended.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
assert isinstance(data, dict)
|
|
|
|
|
|
|
|
|
|
for (name, attribute) in iteritems(self._get_base_attributes()):
|
|
|
|
|
if name in data:
|
|
|
|
|
setattr(self, name, data[name])
|
|
|
|
|
else:
|
|
|
|
|
setattr(self, name, attribute.default)
|
|
|
|
|
|
|
|
|
|
# restore the UUID field
|
|
|
|
|
setattr(self, '_uuid', data.get('uuid'))
|
|
|
|
|
|
|
|
|
|
def _load_vars(self, attr, ds):
|
|
|
|
|
'''
|
|
|
|
|
Vars in a play can be specified either as a dictionary directly, or
|
|
|
|
@ -515,12 +444,43 @@ class Base:
|
|
|
|
|
if not isinstance(new_value, list):
|
|
|
|
|
new_value = [ new_value ]
|
|
|
|
|
|
|
|
|
|
#return list(set(value + new_value))
|
|
|
|
|
return [i for i,_ in itertools.groupby(value + new_value) if i is not None]
|
|
|
|
|
|
|
|
|
|
def __getstate__(self):
|
|
|
|
|
return self.serialize()
|
|
|
|
|
def serialize(self):
|
|
|
|
|
'''
|
|
|
|
|
Serializes the object derived from the base object into
|
|
|
|
|
a dictionary of values. This only serializes the field
|
|
|
|
|
attributes for the object, so this may need to be overridden
|
|
|
|
|
for any classes which wish to add additional items not stored
|
|
|
|
|
as field attributes.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
repr = dict()
|
|
|
|
|
|
|
|
|
|
for name in self._valid_attrs.keys():
|
|
|
|
|
repr[name] = getattr(self, name)
|
|
|
|
|
|
|
|
|
|
# serialize the uuid field
|
|
|
|
|
repr['uuid'] = getattr(self, '_uuid')
|
|
|
|
|
|
|
|
|
|
return repr
|
|
|
|
|
|
|
|
|
|
def deserialize(self, data):
|
|
|
|
|
'''
|
|
|
|
|
Given a dictionary of values, load up the field attributes for
|
|
|
|
|
this object. As with serialize(), if there are any non-field
|
|
|
|
|
attribute data members, this method will need to be overridden
|
|
|
|
|
and extended.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
assert isinstance(data, dict)
|
|
|
|
|
|
|
|
|
|
for (name, attribute) in iteritems(self._valid_attrs):
|
|
|
|
|
if name in data:
|
|
|
|
|
setattr(self, name, data[name])
|
|
|
|
|
else:
|
|
|
|
|
setattr(self, name, attribute.default)
|
|
|
|
|
|
|
|
|
|
# restore the UUID field
|
|
|
|
|
setattr(self, '_uuid', data.get('uuid'))
|
|
|
|
|
|
|
|
|
|
def __setstate__(self, data):
|
|
|
|
|
self.__init__()
|
|
|
|
|
self.deserialize(data)
|
|
|
|
|