Add get_attr which returns all attributes
Add get_attr functionality which returns dict of all resource's attributes. New class expands existing get_attr functionality, so there's no need to add new syntax for it. Also, this functionality should be supported since template version 2015-10-15. implements bp get-attr-all-attributes Change-Id: I01e5690dae6dd9af72f316994c5ec90803844889
This commit is contained in:
parent
0c460f88df
commit
ed56c1bd36
@ -140,8 +140,7 @@ class GetAttThenSelect(cfn_funcs.GetAtt):
|
||||
|
||||
|
||||
class GetAtt(GetAttThenSelect):
|
||||
'''
|
||||
A function for resolving resource attributes.
|
||||
"""A function for resolving resource attributes.
|
||||
|
||||
Takes the form::
|
||||
|
||||
@ -150,7 +149,7 @@ class GetAtt(GetAttThenSelect):
|
||||
- <attribute_name>
|
||||
- <path1>
|
||||
- ...
|
||||
'''
|
||||
"""
|
||||
|
||||
def result(self):
|
||||
path_components = function.resolve(self._path_components)
|
||||
@ -165,6 +164,62 @@ class GetAtt(GetAttThenSelect):
|
||||
return None
|
||||
|
||||
|
||||
class GetAttAllAttributes(GetAtt):
|
||||
"""A function for resolving resource attributes.
|
||||
|
||||
Takes the form::
|
||||
|
||||
get_attr:
|
||||
- <resource_name>
|
||||
- <attributes_name>
|
||||
- <path1>
|
||||
- ...
|
||||
|
||||
where <attributes_name> and <path1>, ... are optional arguments. If there
|
||||
is no <attributes_name>, result will be dict of all resource's attributes.
|
||||
Else function returns resolved resource's attribute.
|
||||
"""
|
||||
|
||||
def _parse_args(self):
|
||||
if not self.args:
|
||||
raise ValueError(_('Arguments to "%s" can be of the next '
|
||||
'forms: [resource_name] or '
|
||||
'[resource_name, attribute, (path), ...]'
|
||||
) % self.fn_name)
|
||||
elif isinstance(self.args, collections.Sequence):
|
||||
if len(self.args) > 1:
|
||||
return super(GetAttAllAttributes, self)._parse_args()
|
||||
else:
|
||||
return self.args[0], None
|
||||
else:
|
||||
raise TypeError(_('Argument to "%s" must be a list') %
|
||||
self.fn_name)
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
"""Check if there is no attribute_name defined, return empty chain."""
|
||||
if self._attribute is not None:
|
||||
return super(GetAttAllAttributes, self).dep_attrs(resource_name)
|
||||
elif self._resource().name == resource_name:
|
||||
res = self._resource()
|
||||
attrs = six.iterkeys(res.attributes_schema)
|
||||
else:
|
||||
attrs = []
|
||||
return itertools.chain(function.dep_attrs(self.args,
|
||||
resource_name), attrs)
|
||||
|
||||
def result(self):
|
||||
if self._attribute is None:
|
||||
r = self._resource()
|
||||
if (r.status in (r.IN_PROGRESS, r.COMPLETE) and
|
||||
r.action in (r.CREATE, r.ADOPT, r.SUSPEND, r.RESUME,
|
||||
r.UPDATE, r.CHECK, r.SNAPSHOT)):
|
||||
return r.FnGetAtts()
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return super(GetAttAllAttributes, self).result()
|
||||
|
||||
|
||||
class Replace(cfn_funcs.Replace):
|
||||
'''
|
||||
A function for performing string substitutions.
|
||||
|
@ -330,7 +330,7 @@ class HOTemplate20150430(HOTemplate20141016):
|
||||
class HOTemplate20151015(HOTemplate20150430):
|
||||
functions = {
|
||||
'digest': hot_funcs.Digest,
|
||||
'get_attr': hot_funcs.GetAtt,
|
||||
'get_attr': hot_funcs.GetAttAllAttributes,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
|
@ -1491,6 +1491,19 @@ class Resource(object):
|
||||
|
||||
return attributes.select_from_attribute(attribute, path)
|
||||
|
||||
def FnGetAtts(self):
|
||||
"""For the intrinsic function get_attr which returns all attributes.
|
||||
|
||||
:returns: dict of all resource's attributes exclude "show" attribute.
|
||||
"""
|
||||
if self.stack.has_cache_data(self.name):
|
||||
attrs = self.stack.cache_data_resource_all_attributes(self.name)
|
||||
else:
|
||||
attrs = dict((k, v) for k, v in six.iteritems(self.attributes))
|
||||
attrs = dict((k, v) for k, v in six.iteritems(attrs)
|
||||
if k != self.SHOW)
|
||||
return attrs
|
||||
|
||||
def FnBase64(self, data):
|
||||
'''
|
||||
For the instrinsic function Fn::Base64.
|
||||
|
@ -1635,6 +1635,10 @@ class Stack(collections.Mapping):
|
||||
return self.cache_data.get(
|
||||
resource_name, {}).get('attrs', {}).get(attribute_key)
|
||||
|
||||
def cache_data_resource_all_attributes(self, resource_name):
|
||||
attrs = self.cache_data.get(resource_name, {}).get('attributes', {})
|
||||
return attrs
|
||||
|
||||
def mark_complete(self, traversal_id):
|
||||
'''
|
||||
Mark the update as complete.
|
||||
|
@ -67,6 +67,20 @@ resources:
|
||||
type: GenericResourceType
|
||||
''')
|
||||
|
||||
hot_tpl_generic_resource_all_attrs = template_format.parse('''
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
resource1:
|
||||
type: GenericResourceType
|
||||
''')
|
||||
|
||||
hot_tpl_complex_attrs_all_attrs = template_format.parse('''
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
resource1:
|
||||
type: ResourceWithComplexAttributesType
|
||||
''')
|
||||
|
||||
hot_tpl_complex_attrs = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
@ -87,6 +101,19 @@ resources:
|
||||
a_map: { get_attr: [ resource1, map] }
|
||||
''')
|
||||
|
||||
hot_tpl_mapped_props_all_attrs = template_format.parse('''
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
resource1:
|
||||
type: ResWithComplexPropsAndAttrs
|
||||
resource2:
|
||||
type: ResWithComplexPropsAndAttrs
|
||||
properties:
|
||||
a_list: { get_attr: [ resource1, list] }
|
||||
a_string: { get_attr: [ resource1, string ] }
|
||||
a_map: { get_attr: [ resource1, map] }
|
||||
''')
|
||||
|
||||
|
||||
class DummyClass(object):
|
||||
metadata = None
|
||||
@ -1323,6 +1350,23 @@ class StackGetAttrValidationTest(common.HeatTestCase):
|
||||
self.assertEqual('',
|
||||
stack.resources['resource2'].properties['a_string'])
|
||||
|
||||
def test_validate_props_from_attrs_all_attrs(self):
|
||||
stack = parser.Stack(self.ctx, 'test_props_from_attrs',
|
||||
template.Template(hot_tpl_mapped_props_all_attrs))
|
||||
stack.resources['resource1'].list = None
|
||||
stack.resources['resource1'].map = None
|
||||
stack.resources['resource1'].string = None
|
||||
try:
|
||||
stack.validate()
|
||||
except exception.StackValidationFailed as exc:
|
||||
self.fail("Validation should have passed: %s" % six.text_type(exc))
|
||||
self.assertEqual([],
|
||||
stack.resources['resource2'].properties['a_list'])
|
||||
self.assertEqual({},
|
||||
stack.resources['resource2'].properties['a_map'])
|
||||
self.assertEqual('',
|
||||
stack.resources['resource2'].properties['a_string'])
|
||||
|
||||
|
||||
class StackParametersTest(common.HeatTestCase):
|
||||
"""
|
||||
@ -1824,3 +1868,107 @@ class HOTParamValidatorTest(common.HeatTestCase):
|
||||
"stack_testit", template.Template(hot_tpl))
|
||||
self.assertEqual(
|
||||
"AllowedPattern must be a string", six.text_type(error))
|
||||
|
||||
|
||||
class TestGetAttAllAttributes(common.HeatTestCase):
|
||||
scenarios = [
|
||||
('test_get_attr_all_attributes', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1']}},
|
||||
expected={'Value': {'Foo': 'resource1', 'foo': 'resource1'}},
|
||||
raises=None
|
||||
)),
|
||||
('test_get_attr_all_attributes_str', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': 'resource1'}},
|
||||
expected='Argument to "get_attr" must be a list',
|
||||
raises=TypeError
|
||||
)),
|
||||
('test_get_attr_all_attributes_invalid_resource_list', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource2']}},
|
||||
raises=exception.InvalidTemplateReference,
|
||||
expected='The specified reference "resource2" '
|
||||
'(in unknown) is incorrect.'
|
||||
)),
|
||||
('test_get_attr_all_attributes_invalid_type', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': {'resource1': 'attr1'}}},
|
||||
raises=TypeError,
|
||||
expected='Argument to "get_attr" must be a list'
|
||||
)),
|
||||
('test_get_attr_all_attributes_invalid_arg_str', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': ''}},
|
||||
raises=ValueError,
|
||||
expected='Arguments to "get_attr" can be of the next '
|
||||
'forms: [resource_name] or '
|
||||
'[resource_name, attribute, (path), ...]'
|
||||
)),
|
||||
('test_get_attr_all_attributes_invalid_arg_list', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': []}},
|
||||
raises=ValueError,
|
||||
expected='Arguments to "get_attr" can be of the next '
|
||||
'forms: [resource_name] or '
|
||||
'[resource_name, attribute, (path), ...]'
|
||||
)),
|
||||
('test_get_attr_all_attributes_standard', dict(
|
||||
hot_tpl=hot_tpl_generic_resource_all_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1', 'foo']}},
|
||||
expected={'Value': 'resource1'},
|
||||
raises=None
|
||||
)),
|
||||
('test_get_attr_all_attrs_complex_attrs', dict(
|
||||
hot_tpl=hot_tpl_complex_attrs_all_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1']}},
|
||||
expected={'Value': {'flat_dict': {'key1': 'val1',
|
||||
'key2': 'val2',
|
||||
'key3': 'val3'},
|
||||
'list': ['foo', 'bar'],
|
||||
'nested_dict': {'dict': {'a': 1,
|
||||
'b': 2,
|
||||
'c': 3},
|
||||
'list': [1, 2, 3],
|
||||
'string': 'abc'},
|
||||
'none': None}},
|
||||
raises=None
|
||||
)),
|
||||
('test_get_attr_all_attrs_complex_attrs_standard', dict(
|
||||
hot_tpl=hot_tpl_complex_attrs_all_attrs,
|
||||
snippet={'Value': {'get_attr': ['resource1', 'list', 1]}},
|
||||
expected={'Value': 'bar'},
|
||||
raises=None
|
||||
)),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def resolve(snippet, template, stack=None):
|
||||
return function.resolve(template.parse(stack, snippet))
|
||||
|
||||
def test_get_attr_all_attributes(self):
|
||||
tmpl = template.Template(self.hot_tpl)
|
||||
stack = parser.Stack(utils.dummy_context(), 'test_get_attr', tmpl)
|
||||
stack.store()
|
||||
stack.create()
|
||||
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||
stack.state)
|
||||
|
||||
rsrc = stack['resource1']
|
||||
for action, status in (
|
||||
(rsrc.CREATE, rsrc.IN_PROGRESS),
|
||||
(rsrc.CREATE, rsrc.COMPLETE),
|
||||
(rsrc.RESUME, rsrc.IN_PROGRESS),
|
||||
(rsrc.RESUME, rsrc.COMPLETE),
|
||||
(rsrc.UPDATE, rsrc.IN_PROGRESS),
|
||||
(rsrc.UPDATE, rsrc.COMPLETE)):
|
||||
rsrc.state_set(action, status)
|
||||
|
||||
if self.raises is not None:
|
||||
ex = self.assertRaises(self.raises,
|
||||
self.resolve, self.snippet, tmpl, stack)
|
||||
self.assertEqual(self.expected, six.text_type(ex))
|
||||
else:
|
||||
self.assertEqual(self.expected,
|
||||
self.resolve(self.snippet, tmpl, stack))
|
||||
|
@ -1406,6 +1406,42 @@ class ResourceTest(common.HeatTestCase):
|
||||
res.FnGetAtt('attr2')
|
||||
self.assertIn("Attribute attr2 is not of type Map", self.LOG.output)
|
||||
|
||||
def test_getatts(self):
|
||||
tmpl = template.Template({
|
||||
'heat_template_version': '2013-05-23',
|
||||
'resources': {
|
||||
'res': {
|
||||
'type': 'ResourceWithComplexAttributesType'
|
||||
}
|
||||
}
|
||||
})
|
||||
stack = parser.Stack(utils.dummy_context(), 'test', tmpl)
|
||||
res = stack['res']
|
||||
self.assertEqual({'list': ['foo', 'bar'],
|
||||
'flat_dict': {'key1': 'val1',
|
||||
'key2': 'val2',
|
||||
'key3': 'val3'},
|
||||
'nested_dict': {'list': [1, 2, 3],
|
||||
'string': 'abc',
|
||||
'dict': {'a': 1, 'b': 2, 'c': 3}},
|
||||
'none': None}, res.FnGetAtts())
|
||||
|
||||
def test_getatts_with_cache_data(self):
|
||||
tmpl = template.Template({
|
||||
'heat_template_version': '2013-05-23',
|
||||
'resources': {
|
||||
'res': {
|
||||
'type': 'ResourceWithPropsType'
|
||||
}
|
||||
}
|
||||
})
|
||||
stack = parser.Stack(utils.dummy_context(), 'test', tmpl,
|
||||
cache_data={
|
||||
'res': {'attributes': {'Foo': 'res',
|
||||
'foo': 'res'}}})
|
||||
res = stack['res']
|
||||
self.assertEqual({'foo': 'res', 'Foo': 'res'}, res.FnGetAtts())
|
||||
|
||||
def test_properties_data_stored_encrypted_decrypted_on_load(self):
|
||||
cfg.CONF.set_override('encrypt_parameters_and_properties', True)
|
||||
|
||||
|
@ -147,6 +147,37 @@ outputs:
|
||||
{get_attr: [BResource, attr_B3]}]
|
||||
"""
|
||||
|
||||
tmpl7 = """
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
BResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [AResource, attr_A2]}
|
||||
metadata:
|
||||
first: {get_attr: [AResource, meta_A1]}
|
||||
CResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [BResource, attr_B2]}
|
||||
metadata:
|
||||
Doo: {get_attr: [BResource, attr_B1]}
|
||||
first: {get_attr: [AResource, meta_A1]}
|
||||
second: {get_attr: [BResource, meta_B2]}
|
||||
outputs:
|
||||
out1:
|
||||
value: [{get_attr: [AResource, attr_A3]},
|
||||
{get_attr: [AResource, attr_A4]},
|
||||
{get_attr: [BResource, attr_B3]},
|
||||
{get_attr: [CResource]}]
|
||||
"""
|
||||
|
||||
|
||||
class DepAttrsTest(common.HeatTestCase):
|
||||
|
||||
@ -183,7 +214,14 @@ class DepAttrsTest(common.HeatTestCase):
|
||||
(u'list', 1),
|
||||
(u'nested_dict', u'dict', u'b'),
|
||||
(u'nested_dict', u'string')]),
|
||||
'BResource': set(['attr_B3'])}))
|
||||
'BResource': set(['attr_B3'])})),
|
||||
('several_res_several_attrs_and_all_attrs',
|
||||
dict(tmpl=tmpl7,
|
||||
expected={'AResource': {'attr_A1', 'attr_A2', 'meta_A1',
|
||||
'attr_A3', 'attr_A4'},
|
||||
'BResource': {'attr_B1', 'attr_B2', 'meta_B2',
|
||||
'attr_B3'},
|
||||
'CResource': {'foo', 'Foo', 'show'}}))
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user