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:
Angus Salkeld 2013-08-23 08:31:54 +10:00
parent 4654f68e84
commit 76281f4fa5
5 changed files with 124 additions and 1 deletions

View File

@ -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'}

View File

@ -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,

View File

@ -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

View File

@ -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):
'''

View File

@ -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'}