Merge "Allow intrinsic functions to be called in any order"

This commit is contained in:
Jenkins 2013-12-09 23:11:24 +00:00 committed by Gerrit Code Review
commit a2da560ad5
4 changed files with 166 additions and 44 deletions

View File

@ -148,7 +148,7 @@ class HOTemplate(template.Template):
return cfn_outputs return cfn_outputs
@staticmethod @staticmethod
def resolve_param_refs(s, parameters): def resolve_param_refs(s, parameters, transform=None):
""" """
Resolve constructs of the form { get_param: my_param } Resolve constructs of the form { get_param: my_param }
""" """
@ -163,10 +163,11 @@ class HOTemplate(template.Template):
except (KeyError, ValueError): except (KeyError, ValueError):
raise exception.UserParameterMissing(key=ref) 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 @staticmethod
def resolve_resource_refs(s, resources): def resolve_resource_refs(s, resources, transform=None):
''' '''
Resolve constructs of the form { "get_resource" : "resource" } Resolve constructs of the form { "get_resource" : "resource" }
''' '''
@ -176,10 +177,11 @@ class HOTemplate(template.Template):
def handle_resource_ref(arg): def handle_resource_ref(arg):
return resources[arg].FnGetRefId() 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 @staticmethod
def resolve_attributes(s, resources): def resolve_attributes(s, resources, transform=None):
""" """
Resolve constructs of the form { get_attr: [my_resource, my_attr] } Resolve constructs of the form { get_attr: [my_resource, my_attr] }
""" """
@ -206,10 +208,11 @@ class HOTemplate(template.Template):
raise exception.InvalidTemplateAttribute(resource=resource, raise exception.InvalidTemplateAttribute(resource=resource,
key=att) key=att)
return template._resolve(match_get_attr, handle_get_attr, s) return template._resolve(match_get_attr, handle_get_attr, s,
transform)
@staticmethod @staticmethod
def resolve_replace(s): def resolve_replace(s, transform=None):
""" """
Resolve template string substitution via function str_replace 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'] match_str_replace = lambda k, v: k in ['str_replace', 'Fn::Replace']
return template._resolve(match_str_replace, return template._resolve(match_str_replace,
handle_str_replace, s) handle_str_replace, s, transform)
def param_schemata(self): def param_schemata(self):
params = self[PARAMETERS].iteritems() params = self[PARAMETERS].iteritems()

View File

@ -728,6 +728,9 @@ def transform(data, transformations):
Apply each of the transformation functions in the supplied list to the data Apply each of the transformation functions in the supplied list to the data
in turn. in turn.
''' '''
def sub_transform(d):
return transform(d, transformations)
for t in transformations: for t in transformations:
data = t(data) data = t(data, transform=sub_transform)
return data return data

View File

@ -87,7 +87,7 @@ class Template(collections.Mapping):
'''Return the number of sections.''' '''Return the number of sections.'''
return len(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", Resolve constructs of the form { "Fn::FindInMap" : [ "mapping",
"key", "key",
@ -101,10 +101,10 @@ class Template(collections.Mapping):
raise KeyError(str(ex)) raise KeyError(str(ex))
return _resolve(lambda k, v: k == 'Fn::FindInMap', return _resolve(lambda k, v: k == 'Fn::FindInMap',
handle_find_in_map, s) handle_find_in_map, s, transform)
@staticmethod @staticmethod
def resolve_availability_zones(s, stack): def resolve_availability_zones(s, stack, transform=None):
''' '''
looking for { "Fn::GetAZs" : "str" } looking for { "Fn::GetAZs" : "str" }
''' '''
@ -118,10 +118,10 @@ class Template(collections.Mapping):
else: else:
return stack.get_availability_zones() 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 @staticmethod
def resolve_param_refs(s, parameters): def resolve_param_refs(s, parameters, transform=None):
''' '''
Resolve constructs of the form { "Ref" : "string" } Resolve constructs of the form { "Ref" : "string" }
''' '''
@ -136,10 +136,10 @@ class Template(collections.Mapping):
except (KeyError, ValueError): except (KeyError, ValueError):
raise exception.UserParameterMissing(key=ref) 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 @staticmethod
def resolve_resource_refs(s, resources): def resolve_resource_refs(s, resources, transform=None):
''' '''
Resolve constructs of the form { "Ref" : "resource" } Resolve constructs of the form { "Ref" : "resource" }
''' '''
@ -149,10 +149,10 @@ class Template(collections.Mapping):
def handle_resource_ref(arg): def handle_resource_ref(arg):
return resources[arg].FnGetRefId() return resources[arg].FnGetRefId()
return _resolve(match_resource_ref, handle_resource_ref, s) return _resolve(match_resource_ref, handle_resource_ref, s, transform)
@staticmethod @staticmethod
def resolve_attributes(s, resources): def resolve_attributes(s, resources, transform=None):
''' '''
Resolve constructs of the form { "Fn::GetAtt" : [ "WebServer", Resolve constructs of the form { "Fn::GetAtt" : [ "WebServer",
"PublicIp" ] } "PublicIp" ] }
@ -173,10 +173,11 @@ class Template(collections.Mapping):
raise exception.InvalidTemplateAttribute(resource=resource, raise exception.InvalidTemplateAttribute(resource=resource,
key=att) 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 @staticmethod
def reduce_joins(s): def reduce_joins(s, transform=None):
''' '''
Reduces contiguous strings in Fn::Join to a single joined string Reduces contiguous strings in Fn::Join to a single joined string
eg the following eg the following
@ -212,10 +213,11 @@ class Template(collections.Mapping):
reduced.append(delim.join(contiguous)) reduced.append(delim.join(contiguous))
return {'Fn::Join': [delim, reduced]} 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 @staticmethod
def resolve_select(s): def resolve_select(s, transform=None):
''' '''
Resolve constructs of the form: Resolve constructs of the form:
(for a list lookup) (for a list lookup)
@ -278,10 +280,11 @@ class Template(collections.Mapping):
raise TypeError(_('Arguments to "Fn::Select" not fully resolved')) 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 @staticmethod
def resolve_joins(s): def resolve_joins(s, transform=None):
''' '''
Resolve constructs of the form { "Fn::Join" : [ "delim", [ "str1", Resolve constructs of the form { "Fn::Join" : [ "delim", [ "str1",
"str2" ] } "str2" ] }
@ -310,10 +313,11 @@ class Template(collections.Mapping):
return delim.join(empty_for_none(value) for value in strings) 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 @staticmethod
def resolve_split(s): def resolve_split(s, transform=None):
''' '''
Split strings in Fn::Split to a list of sub strings Split strings in Fn::Split to a list of sub strings
eg the following eg the following
@ -336,10 +340,11 @@ class Template(collections.Mapping):
'"Fn::Split" should be: %s') % '"Fn::Split" should be: %s') %
example) example)
return strings.split(delim) 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 @staticmethod
def resolve_replace(s): def resolve_replace(s, transform=None):
""" """
Resolve constructs of the form:: Resolve constructs of the form::
@ -384,10 +389,11 @@ class Template(collections.Mapping):
string = string.replace(k, v) string = string.replace(k, v)
return string 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 @staticmethod
def resolve_base64(s): def resolve_base64(s, transform=None):
''' '''
Resolve constructs of the form { "Fn::Base64" : "string" } Resolve constructs of the form { "Fn::Base64" : "string" }
''' '''
@ -397,10 +403,11 @@ class Template(collections.Mapping):
'not fully resolved')) 'not fully resolved'))
return string 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 @staticmethod
def resolve_member_list_to_map(s): def resolve_member_list_to_map(s, transform=None):
''' '''
Resolve constructs of the form:: Resolve constructs of the form::
@ -437,10 +444,10 @@ class Template(collections.Mapping):
valuename=args[1]) valuename=args[1])
return _resolve(lambda k, v: k == 'Fn::MemberListToMap', return _resolve(lambda k, v: k == 'Fn::MemberListToMap',
handle_member_list_to_map, s) handle_member_list_to_map, s, transform)
@staticmethod @staticmethod
def resolve_resource_facade(s, stack): def resolve_resource_facade(s, stack, transform=None):
''' '''
Resolve constructs of the form {'Fn::ResourceFacade': 'Metadata'} Resolve constructs of the form {'Fn::ResourceFacade': 'Metadata'}
''' '''
@ -462,29 +469,34 @@ class Template(collections.Mapping):
return _resolve(lambda k, v: k == 'Fn::ResourceFacade', return _resolve(lambda k, v: k == 'Fn::ResourceFacade',
handle_resource_facade, handle_resource_facade,
s) s, transform)
def param_schemata(self): def param_schemata(self):
parameters = self[PARAMETERS].iteritems() parameters = self[PARAMETERS].iteritems()
return dict((name, ParamSchema(schema)) for name, schema in parameters) 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 Resolve constructs in a snippet of a template. The supplied match function
should return True if a particular key-value pair should be substituted, should return True if a particular key-value pair should be substituted,
and the handle function should return the correct substitution when passed and the handle function should return the correct substitution when passed
the argument list as parameters. 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 isinstance(snippet, dict):
if len(snippet) == 1: if len(snippet) == 1:
k, v = snippet.items()[0] k, v = snippet.items()[0]
if match(k, v): 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()) return dict((k, recurse(v)) for k, v in snippet.items())
elif isinstance(snippet, list): elif isinstance(snippet, list):
return [recurse(s) for s in snippet] return [recurse(s) for s in snippet]

View File

@ -106,11 +106,6 @@ class ParserTest(HeatTestCase):
self.assertEqual(parsed['blarg'], raw['blarg']) self.assertEqual(parsed['blarg'], raw['blarg'])
self.assertTrue(parsed is not raw) 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('''{ mapping_template = template_format.parse('''{
"Mappings" : { "Mappings" : {
@ -608,6 +603,115 @@ class TemplateFnErrorTest(HeatTestCase):
self.assertIn(self.snippet.keys()[0], str(error)) 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): class StackTest(HeatTestCase):
def setUp(self): def setUp(self):
super(StackTest, self).setUp() super(StackTest, self).setUp()