Merge "Allow intrinsic functions to be called in any order"
This commit is contained in:
commit
a2da560ad5
@ -148,7 +148,7 @@ class HOTemplate(template.Template):
|
||||
return cfn_outputs
|
||||
|
||||
@staticmethod
|
||||
def resolve_param_refs(s, parameters):
|
||||
def resolve_param_refs(s, parameters, transform=None):
|
||||
"""
|
||||
Resolve constructs of the form { get_param: my_param }
|
||||
"""
|
||||
@ -163,10 +163,11 @@ class HOTemplate(template.Template):
|
||||
except (KeyError, ValueError):
|
||||
raise exception.UserParameterMissing(key=ref)
|
||||
|
||||
return template._resolve(match_param_ref, handle_param_ref, s)
|
||||
return template._resolve(match_param_ref, handle_param_ref, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_resource_refs(s, resources):
|
||||
def resolve_resource_refs(s, resources, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "get_resource" : "resource" }
|
||||
'''
|
||||
@ -176,10 +177,11 @@ class HOTemplate(template.Template):
|
||||
def handle_resource_ref(arg):
|
||||
return resources[arg].FnGetRefId()
|
||||
|
||||
return template._resolve(match_resource_ref, handle_resource_ref, s)
|
||||
return template._resolve(match_resource_ref, handle_resource_ref, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_attributes(s, resources):
|
||||
def resolve_attributes(s, resources, transform=None):
|
||||
"""
|
||||
Resolve constructs of the form { get_attr: [my_resource, my_attr] }
|
||||
"""
|
||||
@ -206,10 +208,11 @@ class HOTemplate(template.Template):
|
||||
raise exception.InvalidTemplateAttribute(resource=resource,
|
||||
key=att)
|
||||
|
||||
return template._resolve(match_get_attr, handle_get_attr, s)
|
||||
return template._resolve(match_get_attr, handle_get_attr, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_replace(s):
|
||||
def resolve_replace(s, transform=None):
|
||||
"""
|
||||
Resolve template string substitution via function str_replace
|
||||
|
||||
@ -253,7 +256,7 @@ class HOTemplate(template.Template):
|
||||
|
||||
match_str_replace = lambda k, v: k in ['str_replace', 'Fn::Replace']
|
||||
return template._resolve(match_str_replace,
|
||||
handle_str_replace, s)
|
||||
handle_str_replace, s, transform)
|
||||
|
||||
def param_schemata(self):
|
||||
params = self[PARAMETERS].iteritems()
|
||||
|
@ -728,6 +728,9 @@ def transform(data, transformations):
|
||||
Apply each of the transformation functions in the supplied list to the data
|
||||
in turn.
|
||||
'''
|
||||
def sub_transform(d):
|
||||
return transform(d, transformations)
|
||||
|
||||
for t in transformations:
|
||||
data = t(data)
|
||||
data = t(data, transform=sub_transform)
|
||||
return data
|
||||
|
@ -87,7 +87,7 @@ class Template(collections.Mapping):
|
||||
'''Return the number of sections.'''
|
||||
return len(SECTIONS)
|
||||
|
||||
def resolve_find_in_map(self, s):
|
||||
def resolve_find_in_map(self, s, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Fn::FindInMap" : [ "mapping",
|
||||
"key",
|
||||
@ -101,10 +101,10 @@ class Template(collections.Mapping):
|
||||
raise KeyError(str(ex))
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::FindInMap',
|
||||
handle_find_in_map, s)
|
||||
handle_find_in_map, s, transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_availability_zones(s, stack):
|
||||
def resolve_availability_zones(s, stack, transform=None):
|
||||
'''
|
||||
looking for { "Fn::GetAZs" : "str" }
|
||||
'''
|
||||
@ -118,10 +118,10 @@ class Template(collections.Mapping):
|
||||
else:
|
||||
return stack.get_availability_zones()
|
||||
|
||||
return _resolve(match_get_az, handle_get_az, s)
|
||||
return _resolve(match_get_az, handle_get_az, s, transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_param_refs(s, parameters):
|
||||
def resolve_param_refs(s, parameters, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Ref" : "string" }
|
||||
'''
|
||||
@ -136,10 +136,10 @@ class Template(collections.Mapping):
|
||||
except (KeyError, ValueError):
|
||||
raise exception.UserParameterMissing(key=ref)
|
||||
|
||||
return _resolve(match_param_ref, handle_param_ref, s)
|
||||
return _resolve(match_param_ref, handle_param_ref, s, transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_resource_refs(s, resources):
|
||||
def resolve_resource_refs(s, resources, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Ref" : "resource" }
|
||||
'''
|
||||
@ -149,10 +149,10 @@ class Template(collections.Mapping):
|
||||
def handle_resource_ref(arg):
|
||||
return resources[arg].FnGetRefId()
|
||||
|
||||
return _resolve(match_resource_ref, handle_resource_ref, s)
|
||||
return _resolve(match_resource_ref, handle_resource_ref, s, transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_attributes(s, resources):
|
||||
def resolve_attributes(s, resources, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Fn::GetAtt" : [ "WebServer",
|
||||
"PublicIp" ] }
|
||||
@ -173,10 +173,11 @@ class Template(collections.Mapping):
|
||||
raise exception.InvalidTemplateAttribute(resource=resource,
|
||||
key=att)
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def reduce_joins(s):
|
||||
def reduce_joins(s, transform=None):
|
||||
'''
|
||||
Reduces contiguous strings in Fn::Join to a single joined string
|
||||
eg the following
|
||||
@ -212,10 +213,11 @@ class Template(collections.Mapping):
|
||||
reduced.append(delim.join(contiguous))
|
||||
return {'Fn::Join': [delim, reduced]}
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_select(s):
|
||||
def resolve_select(s, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form:
|
||||
(for a list lookup)
|
||||
@ -278,10 +280,11 @@ class Template(collections.Mapping):
|
||||
|
||||
raise TypeError(_('Arguments to "Fn::Select" not fully resolved'))
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Select', handle_select, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Select', handle_select, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_joins(s):
|
||||
def resolve_joins(s, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Fn::Join" : [ "delim", [ "str1",
|
||||
"str2" ] }
|
||||
@ -310,10 +313,11 @@ class Template(collections.Mapping):
|
||||
|
||||
return delim.join(empty_for_none(value) for value in strings)
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_split(s):
|
||||
def resolve_split(s, transform=None):
|
||||
'''
|
||||
Split strings in Fn::Split to a list of sub strings
|
||||
eg the following
|
||||
@ -336,10 +340,11 @@ class Template(collections.Mapping):
|
||||
'"Fn::Split" should be: %s') %
|
||||
example)
|
||||
return strings.split(delim)
|
||||
return _resolve(lambda k, v: k == 'Fn::Split', handle_split, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Split', handle_split, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_replace(s):
|
||||
def resolve_replace(s, transform=None):
|
||||
"""
|
||||
Resolve constructs of the form::
|
||||
|
||||
@ -384,10 +389,11 @@ class Template(collections.Mapping):
|
||||
string = string.replace(k, v)
|
||||
return string
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Replace', handle_replace, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Replace', handle_replace, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_base64(s):
|
||||
def resolve_base64(s, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form { "Fn::Base64" : "string" }
|
||||
'''
|
||||
@ -397,10 +403,11 @@ class Template(collections.Mapping):
|
||||
'not fully resolved'))
|
||||
return string
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
|
||||
return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s,
|
||||
transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_member_list_to_map(s):
|
||||
def resolve_member_list_to_map(s, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form::
|
||||
|
||||
@ -437,10 +444,10 @@ class Template(collections.Mapping):
|
||||
valuename=args[1])
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::MemberListToMap',
|
||||
handle_member_list_to_map, s)
|
||||
handle_member_list_to_map, s, transform)
|
||||
|
||||
@staticmethod
|
||||
def resolve_resource_facade(s, stack):
|
||||
def resolve_resource_facade(s, stack, transform=None):
|
||||
'''
|
||||
Resolve constructs of the form {'Fn::ResourceFacade': 'Metadata'}
|
||||
'''
|
||||
@ -462,29 +469,34 @@ class Template(collections.Mapping):
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::ResourceFacade',
|
||||
handle_resource_facade,
|
||||
s)
|
||||
s, transform)
|
||||
|
||||
def param_schemata(self):
|
||||
parameters = self[PARAMETERS].iteritems()
|
||||
return dict((name, ParamSchema(schema)) for name, schema in parameters)
|
||||
|
||||
|
||||
def _resolve(match, handle, snippet):
|
||||
def _resolve(match, handle, snippet, transform=None):
|
||||
'''
|
||||
Resolve constructs in a snippet of a template. The supplied match function
|
||||
should return True if a particular key-value pair should be substituted,
|
||||
and the handle function should return the correct substitution when passed
|
||||
the argument list as parameters.
|
||||
|
||||
Returns a copy of the original snippet with the substitutions performed.
|
||||
Returns a copy of the original snippet with the substitutions to handle
|
||||
functions performed.
|
||||
'''
|
||||
recurse = lambda s: _resolve(match, handle, s)
|
||||
|
||||
recurse = lambda s: _resolve(match, handle, s, transform)
|
||||
|
||||
if isinstance(snippet, dict):
|
||||
if len(snippet) == 1:
|
||||
k, v = snippet.items()[0]
|
||||
if match(k, v):
|
||||
return handle(recurse(v))
|
||||
if transform:
|
||||
return handle(transform(v))
|
||||
else:
|
||||
return handle(v)
|
||||
return dict((k, recurse(v)) for k, v in snippet.items())
|
||||
elif isinstance(snippet, list):
|
||||
return [recurse(s) for s in snippet]
|
||||
|
@ -106,11 +106,6 @@ class ParserTest(HeatTestCase):
|
||||
self.assertEqual(parsed['blarg'], raw['blarg'])
|
||||
self.assertTrue(parsed is not raw)
|
||||
|
||||
def test_join_recursive(self):
|
||||
raw = {'Fn::Join': ['\n', [{'Fn::Join':
|
||||
[' ', ['foo', 'bar']]}, 'baz']]}
|
||||
self.assertEqual(join(raw), 'foo bar\nbaz')
|
||||
|
||||
|
||||
mapping_template = template_format.parse('''{
|
||||
"Mappings" : {
|
||||
@ -608,6 +603,115 @@ class TemplateFnErrorTest(HeatTestCase):
|
||||
self.assertIn(self.snippet.keys()[0], str(error))
|
||||
|
||||
|
||||
class ResolveDataTest(HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ResolveDataTest, self).setUp()
|
||||
self.username = 'parser_stack_test_user'
|
||||
|
||||
utils.setup_dummy_db()
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'resolve_test_stack',
|
||||
template.Template({}),
|
||||
environment.Environment({}))
|
||||
|
||||
def test_split_join_split_join(self):
|
||||
# each snippet in this test encapsulates
|
||||
# the snippet from the previous step, leading
|
||||
# to increasingly nested function calls
|
||||
|
||||
# split
|
||||
snippet = {'Fn::Split': [',', 'one,two,three']}
|
||||
self.assertEqual(['one', 'two', 'three'],
|
||||
self.stack.resolve_runtime_data(snippet))
|
||||
|
||||
# split then join
|
||||
snippet = {'Fn::Join': [';', snippet]}
|
||||
self.assertEqual('one;two;three',
|
||||
self.stack.resolve_runtime_data(snippet))
|
||||
|
||||
# split then join then split
|
||||
snippet = {'Fn::Split': [';', snippet]}
|
||||
self.assertEqual(['one', 'two', 'three'],
|
||||
self.stack.resolve_runtime_data(snippet))
|
||||
|
||||
# split then join then split then join
|
||||
snippet = {'Fn::Join': ['-', snippet]}
|
||||
self.assertEqual('one-two-three',
|
||||
self.stack.resolve_runtime_data(snippet))
|
||||
|
||||
def test_join_recursive(self):
|
||||
raw = {'Fn::Join': ['\n', [{'Fn::Join':
|
||||
[' ', ['foo', 'bar']]}, 'baz']]}
|
||||
self.assertEqual('foo bar\nbaz', self.stack.resolve_runtime_data(raw))
|
||||
|
||||
def test_base64_replace(self):
|
||||
raw = {'Fn::Base64': {'Fn::Replace': [
|
||||
{'foo': 'bar'}, 'Meet at the foo']}}
|
||||
self.assertEqual('Meet at the bar',
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
def test_replace_base64(self):
|
||||
raw = {'Fn::Replace': [{'foo': 'bar'}, {
|
||||
'Fn::Base64': 'Meet at the foo'}]}
|
||||
self.assertEqual('Meet at the bar',
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
def test_nested_selects(self):
|
||||
data = {
|
||||
'a': ['one', 'two', 'three'],
|
||||
'b': ['een', 'twee', {'d': 'D', 'e': 'E'}]
|
||||
}
|
||||
raw = {'Fn::Select': ['a', data]}
|
||||
self.assertEqual(data['a'],
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
raw = {'Fn::Select': ['b', data]}
|
||||
self.assertEqual(data['b'],
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
raw = {
|
||||
'Fn::Select': ['1', {
|
||||
'Fn::Select': ['b', data]}]}
|
||||
self.assertEqual('twee',
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
raw = {
|
||||
'Fn::Select': ['e', {
|
||||
'Fn::Select': ['2', {
|
||||
'Fn::Select': ['b', data]}]}]}
|
||||
self.assertEqual('E',
|
||||
self.stack.resolve_runtime_data(raw))
|
||||
|
||||
def test_member_list_select(self):
|
||||
snippet = {'Fn::Select': ['metric', {"Fn::MemberListToMap": [
|
||||
'Name', 'Value', ['.member.0.Name=metric',
|
||||
'.member.0.Value=cpu',
|
||||
'.member.1.Name=size',
|
||||
'.member.1.Value=56']]}]}
|
||||
self.assertEqual('cpu',
|
||||
self.stack.resolve_runtime_data(snippet))
|
||||
|
||||
def test_join_reduce(self):
|
||||
join = {"Fn::Join": [" ", ["foo", "bar", "baz", {'Ref': 'baz'},
|
||||
"bink", "bonk"]]}
|
||||
self.assertEqual(
|
||||
{"Fn::Join": [" ", ["foo bar baz", {'Ref': 'baz'}, "bink bonk"]]},
|
||||
self.stack.resolve_static_data(join))
|
||||
|
||||
join = {"Fn::Join": [" ", ["foo", {'Ref': 'baz'},
|
||||
"bink"]]}
|
||||
self.assertEqual(
|
||||
{"Fn::Join": [" ", ["foo", {'Ref': 'baz'}, "bink"]]},
|
||||
self.stack.resolve_static_data(join))
|
||||
|
||||
join = {"Fn::Join": [" ", [{'Ref': 'baz'}]]}
|
||||
self.assertEqual(
|
||||
{"Fn::Join": [" ", [{'Ref': 'baz'}]]},
|
||||
self.stack.resolve_static_data(join))
|
||||
|
||||
|
||||
class StackTest(HeatTestCase):
|
||||
def setUp(self):
|
||||
super(StackTest, self).setUp()
|
||||
|
Loading…
Reference in New Issue
Block a user