Implement Fn::MemberListToMap
This is to allow the CloudWatch::Alarm to be implemented as a resource template. The Dimensions need to be converted from [{Name: bla, Value: foo}] into a normal dict. So we define the Dimensions as a CommaDelimitedList in the template, then in TemplateResource we see that the property is a list of dicts and convert it into the aws style memberlist '.member.0.Name=bla,.member.0.Value=green' then in the CW template we can do the following: matching_metadata: "Fn::MemberListToMap": [Name, Value, {"Fn:: Split": [",", {Ref: Dimensions}]}] Note: this in not a single case usage as we can use this for the Tags property that is in a lot of other resources. Change-Id: I68910c51eaeb0857531028e87b89b9192db4c8ba
This commit is contained in:
parent
4654f68e84
commit
76281f4fa5
@ -294,3 +294,31 @@ To use it
|
||||
What happened is the metadata in ``top.yaml`` (key: value, some: more
|
||||
stuff) gets passed into the resource template via the `Fn::ResourceFacade`_
|
||||
function.
|
||||
|
||||
-------------------
|
||||
Fn::MemberListToMap
|
||||
-------------------
|
||||
Convert an AWS style member list into a map.
|
||||
|
||||
Parameters
|
||||
~~~~~~~~~~
|
||||
key name: string
|
||||
The name of the key (normally "Name" or "Key")
|
||||
|
||||
value name: string
|
||||
The name of the value (normally "Value")
|
||||
|
||||
list: A list of strings
|
||||
The string to convert.
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
::
|
||||
|
||||
{'Fn::MemberListToMap': ['Name', 'Value', ['.member.0.Name=key',
|
||||
'.member.0.Value=door',
|
||||
'.member.1.Name=colour',
|
||||
'.member.1.Value=green']]}
|
||||
|
||||
returns
|
||||
{'key': 'door', 'colour': 'green'}
|
||||
|
@ -574,6 +574,7 @@ def resolve_runtime_data(template, resources, snippet):
|
||||
functools.partial(template.resolve_attributes,
|
||||
resources=resources),
|
||||
template.resolve_split,
|
||||
template.resolve_member_list_to_map,
|
||||
template.resolve_select,
|
||||
template.resolve_joins,
|
||||
template.resolve_replace,
|
||||
|
@ -78,7 +78,15 @@ class TemplateResource(stack_resource.StackResource):
|
||||
if val is not None:
|
||||
# take a list and create a CommaDelimitedList
|
||||
if v.type() == properties.LIST:
|
||||
val = ','.join(val)
|
||||
if isinstance(val[0], dict):
|
||||
flattened = []
|
||||
for (i, item) in enumerate(val):
|
||||
for (k, v) in iter(item.items()):
|
||||
mem_str = '.member.%d.%s=%s' % (i, k, v)
|
||||
flattened.append(mem_str)
|
||||
params[n] = ','.join(flattened)
|
||||
else:
|
||||
val = ','.join(val)
|
||||
|
||||
# for MAP, the JSON param takes either a collection or string,
|
||||
# so just pass it on and let the param validate as appropriate
|
||||
|
@ -16,6 +16,7 @@
|
||||
import collections
|
||||
import json
|
||||
|
||||
from heat.api.aws import utils as aws_utils
|
||||
from heat.db import api as db_api
|
||||
from heat.common import exception
|
||||
from heat.engine.parameters import ParamSchema
|
||||
@ -361,6 +362,44 @@ class Template(collections.Mapping):
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
|
||||
|
||||
@staticmethod
|
||||
def resolve_member_list_to_map(s):
|
||||
'''
|
||||
Resolve constructs of the form
|
||||
{'Fn::MemberListToMap': ['Name', 'Value', ['.member.0.Name=key',
|
||||
'.member.0.Value=door']]}
|
||||
the first two arguments are the names of the key and value.
|
||||
'''
|
||||
|
||||
def handle_member_list_to_map(args):
|
||||
correct = '''
|
||||
{'Fn::MemberListToMap': ['Name', 'Value',
|
||||
['.member.0.Name=key',
|
||||
'.member.0.Value=door']]}
|
||||
'''
|
||||
if not isinstance(args, (list, tuple)):
|
||||
raise TypeError('Wrong Arguments try: "%s"' % correct)
|
||||
if len(args) != 3:
|
||||
raise TypeError('Wrong Arguments try: "%s"' % correct)
|
||||
if not isinstance(args[0], basestring):
|
||||
raise TypeError('Wrong Arguments try: "%s"' % correct)
|
||||
if not isinstance(args[1], basestring):
|
||||
raise TypeError('Wrong Arguments try: "%s"' % correct)
|
||||
if not isinstance(args[2], (list, tuple)):
|
||||
raise TypeError('Wrong Arguments try: "%s"' % correct)
|
||||
|
||||
partial = {}
|
||||
for item in args[2]:
|
||||
sp = item.split('=')
|
||||
partial[sp[0]] = sp[1]
|
||||
return aws_utils.extract_param_pairs(partial,
|
||||
prefix='',
|
||||
keyname=args[0],
|
||||
valuename=args[1])
|
||||
|
||||
return _resolve(lambda k, v: k == 'Fn::MemberListToMap',
|
||||
handle_member_list_to_map, s)
|
||||
|
||||
@staticmethod
|
||||
def resolve_resource_facade(s, stack):
|
||||
'''
|
||||
|
@ -461,6 +461,53 @@ Mappings:
|
||||
parser.Template.resolve_replace(snippet),
|
||||
'"foo" is "${var3}"')
|
||||
|
||||
def test_member_list2map_good(self):
|
||||
snippet = {"Fn::MemberListToMap": [
|
||||
'Name', 'Value', ['.member.0.Name=metric',
|
||||
'.member.0.Value=cpu',
|
||||
'.member.1.Name=size',
|
||||
'.member.1.Value=56']]}
|
||||
self.assertEqual(
|
||||
{'metric': 'cpu', 'size': '56'},
|
||||
parser.Template.resolve_member_list_to_map(snippet))
|
||||
|
||||
def test_member_list2map_good2(self):
|
||||
snippet = {"Fn::MemberListToMap": [
|
||||
'Key', 'Value', ['.member.2.Key=metric',
|
||||
'.member.2.Value=cpu',
|
||||
'.member.5.Key=size',
|
||||
'.member.5.Value=56']]}
|
||||
self.assertEqual(
|
||||
{'metric': 'cpu', 'size': '56'},
|
||||
parser.Template.resolve_member_list_to_map(snippet))
|
||||
|
||||
def test_member_list2map_no_key_or_val(self):
|
||||
snippet = {"Fn::MemberListToMap": [
|
||||
'Key', ['.member.2.Key=metric',
|
||||
'.member.2.Value=cpu',
|
||||
'.member.5.Key=size',
|
||||
'.member.5.Value=56']]}
|
||||
self.assertRaises(TypeError,
|
||||
parser.Template.resolve_member_list_to_map,
|
||||
snippet)
|
||||
|
||||
def test_member_list2map_no_list(self):
|
||||
snippet = {"Fn::MemberListToMap": [
|
||||
'Key', '.member.2.Key=metric']}
|
||||
self.assertRaises(TypeError,
|
||||
parser.Template.resolve_member_list_to_map,
|
||||
snippet)
|
||||
|
||||
def test_member_list2map_not_string(self):
|
||||
snippet = {"Fn::MemberListToMap": [
|
||||
'Name', ['Value'], ['.member.0.Name=metric',
|
||||
'.member.0.Value=cpu',
|
||||
'.member.1.Name=size',
|
||||
'.member.1.Value=56']]}
|
||||
self.assertRaises(TypeError,
|
||||
parser.Template.resolve_member_list_to_map,
|
||||
snippet)
|
||||
|
||||
def test_resource_facade(self):
|
||||
metadata_snippet = {'Fn::ResourceFacade': 'Metadata'}
|
||||
deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
|
||||
|
Loading…
Reference in New Issue
Block a user