Merge "Implement new map_merge intrinsic function"
This commit is contained in:
commit
9755629750
@ -179,6 +179,25 @@ For example, Heat currently supports the following values for the
|
||||
str_replace
|
||||
str_split
|
||||
|
||||
2016-04-08
|
||||
The key with value ``2016-04-08`` indicates that the YAML document is a HOT
|
||||
template and it may contain features added and/or removed up until the
|
||||
Mitaka release. This version also adds the map_merge function which
|
||||
can be used to merge the contents of maps. The complete list of supported
|
||||
functions is::
|
||||
|
||||
digest
|
||||
get_attr
|
||||
get_file
|
||||
get_param
|
||||
get_resource
|
||||
list_join
|
||||
map_merge
|
||||
repeat
|
||||
resource_facade
|
||||
str_replace
|
||||
str_split
|
||||
|
||||
.. _hot_spec_parameter_groups:
|
||||
|
||||
Parameter groups section
|
||||
@ -1209,3 +1228,28 @@ The result of which is:
|
||||
|
||||
Note: The index starts at zero, and any value outside the maximum (e.g the
|
||||
length of the list minus one) will cause an error.
|
||||
|
||||
map_merge
|
||||
---------
|
||||
The ``map_merge`` function merges maps together. Values in the latter maps
|
||||
override any values in earlier ones. Can be very useful when composing maps
|
||||
that contain configuration data into a single consolidated map.
|
||||
|
||||
The syntax of the ``map_merge`` function is
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
map_merge:
|
||||
- <map 1>
|
||||
- <map 2>
|
||||
- ...
|
||||
|
||||
For example
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
map_merge: [{'k1': 'v1', 'k2': 'v2'}, {'k1': 'v2'}]
|
||||
|
||||
This resolves to a map containing ``{'k1': 'v2', 'k2': 'v2'}``.
|
||||
|
||||
Maps containing no items resolve to {}.
|
||||
|
@ -429,6 +429,47 @@ class JoinMultiple(function.Function):
|
||||
return delim.join(ensure_string(s) for s in strings)
|
||||
|
||||
|
||||
class MapMerge(function.Function):
|
||||
"""A function for merging maps.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "map_merge" : [{'k1': 'v1', 'k2': 'v2'}, {'k1': 'v2'}] }
|
||||
|
||||
And resolves to::
|
||||
|
||||
{'k1': 'v2', 'k2': 'v2'}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(MapMerge, self).__init__(stack, fn_name, args)
|
||||
example = (_('"%s" : [ { "key1": "val1" }, { "key2": "val2" } ]')
|
||||
% fn_name)
|
||||
self.fmt_data = {'fn_name': fn_name, 'example': example}
|
||||
|
||||
def result(self):
|
||||
args = function.resolve(self.args)
|
||||
|
||||
if not isinstance(args, collections.Sequence):
|
||||
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be: %(example)s') % self.fmt_data)
|
||||
|
||||
def ensure_map(m):
|
||||
if m is None:
|
||||
return {}
|
||||
elif isinstance(m, collections.Mapping):
|
||||
return m
|
||||
else:
|
||||
msg = _('Incorrect arguments: Items to merge must be maps.')
|
||||
raise TypeError(msg)
|
||||
|
||||
ret_map = {}
|
||||
for m in args:
|
||||
ret_map.update(ensure_map(m))
|
||||
return ret_map
|
||||
|
||||
|
||||
class ResourceFacade(cfn_funcs.ResourceFacade):
|
||||
"""A function for retrieving data in a parent provider template.
|
||||
|
||||
|
@ -363,3 +363,36 @@ class HOTemplate20151015(HOTemplate20150430):
|
||||
'Fn::ResourceFacade': hot_funcs.Removed,
|
||||
'Ref': hot_funcs.Removed,
|
||||
}
|
||||
|
||||
|
||||
class HOTemplate20160408(HOTemplate20151015):
|
||||
functions = {
|
||||
'digest': hot_funcs.Digest,
|
||||
'get_attr': hot_funcs.GetAttAllAttributes,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'list_join': hot_funcs.JoinMultiple,
|
||||
'repeat': hot_funcs.Repeat,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
'str_replace': hot_funcs.ReplaceJson,
|
||||
|
||||
# functions added since 20151015
|
||||
'map_merge': hot_funcs.MapMerge,
|
||||
|
||||
# functions added since 20150430
|
||||
'str_split': hot_funcs.StrSplit,
|
||||
|
||||
# functions removed from 20150430
|
||||
'Fn::Select': hot_funcs.Removed,
|
||||
|
||||
# functions removed from 20130523
|
||||
'Fn::GetAZs': hot_funcs.Removed,
|
||||
'Fn::Join': hot_funcs.Removed,
|
||||
'Fn::Split': hot_funcs.Removed,
|
||||
'Fn::Replace': hot_funcs.Removed,
|
||||
'Fn::Base64': hot_funcs.Removed,
|
||||
'Fn::MemberListToMap': hot_funcs.Removed,
|
||||
'Fn::ResourceFacade': hot_funcs.Removed,
|
||||
'Ref': hot_funcs.Removed,
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ hot_liberty_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2015-10-15
|
||||
''')
|
||||
|
||||
hot_mitaka_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2016-04-08
|
||||
''')
|
||||
|
||||
hot_tpl_empty_sections = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
parameters:
|
||||
@ -708,6 +712,35 @@ class HOTemplateTest(common.HeatTestCase):
|
||||
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
|
||||
self.assertIn('Incorrect arguments', six.text_type(exc))
|
||||
|
||||
def test_merge(self):
|
||||
snippet = {'map_merge': [{'f1': 'b1', 'f2': 'b2'}, {'f1': 'b2'}]}
|
||||
tmpl = template.Template(hot_mitaka_tpl_empty)
|
||||
resolved = self.resolve(snippet, tmpl)
|
||||
self.assertEqual('b2', resolved['f1'])
|
||||
self.assertEqual('b2', resolved['f2'])
|
||||
|
||||
def test_merge_none(self):
|
||||
snippet = {'map_merge': [{'f1': 'b1', 'f2': 'b2'}, None]}
|
||||
tmpl = template.Template(hot_mitaka_tpl_empty)
|
||||
resolved = self.resolve(snippet, tmpl)
|
||||
self.assertEqual('b1', resolved['f1'])
|
||||
self.assertEqual('b2', resolved['f2'])
|
||||
|
||||
def test_merge_invalid(self):
|
||||
snippet = {'map_merge': [{'f1': 'b1', 'f2': 'b2'}, ['f1', 'b2']]}
|
||||
tmpl = template.Template(hot_mitaka_tpl_empty)
|
||||
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
|
||||
self.assertIn('Incorrect arguments', six.text_type(exc))
|
||||
|
||||
def test_merge_containing_repeat(self):
|
||||
snippet = {'map_merge': {'repeat': {'template': {'ROLE': 'ROLE'},
|
||||
'for_each': {'ROLE': ['role1', 'role2']}}}}
|
||||
tmpl = template.Template(hot_mitaka_tpl_empty)
|
||||
resolved = self.resolve(snippet, tmpl)
|
||||
|
||||
self.assertEqual('role1', resolved['role1'])
|
||||
self.assertEqual('role2', resolved['role2'])
|
||||
|
||||
def test_repeat(self):
|
||||
"""Test repeat function."""
|
||||
hot_tpl = template_format.parse('''
|
||||
|
@ -451,7 +451,7 @@ class TemplateTest(common.HeatTestCase):
|
||||
init_ex = self.assertRaises(exception.InvalidTemplateVersion,
|
||||
template.Template, invalid_hot_version_tmp)
|
||||
valid_versions = ['2013-05-23', '2014-10-16',
|
||||
'2015-04-30', '2015-10-15']
|
||||
'2015-04-30', '2015-10-15', '2016-04-08']
|
||||
ex_error_msg = ('The template version is invalid: '
|
||||
'"heat_template_version: 2012-12-12". '
|
||||
'"heat_template_version" should be one of: %s'
|
||||
|
@ -109,6 +109,7 @@ heat.templates =
|
||||
heat_template_version.2014-10-16 = heat.engine.hot.template:HOTemplate20141016
|
||||
heat_template_version.2015-04-30 = heat.engine.hot.template:HOTemplate20150430
|
||||
heat_template_version.2015-10-15 = heat.engine.hot.template:HOTemplate20151015
|
||||
heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408
|
||||
HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:HeatTemplate
|
||||
AWSTemplateFormatVersion.2010-09-09 = heat.engine.cfn.template:CfnTemplate
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user