Allow map/list items for str_replace
Currently any attempt to substitute placeholders using data from a json/map or list parameter/attribute results in an error, which is inconvenient if you wish to provide such data to an instance, e.g so it can be used in a script via jq/python or whatever. So, tolerate non-string parameter values, and attempt to serialize them as json, then substitute the resulting json strings. Change-Id: I362cc76ac3f68d4649a62459455c0dcae2dcd25d Closes-Bug: #1489028
This commit is contained in:
parent
b8fb5c61e7
commit
66f4178d28
@ -1099,7 +1099,11 @@ template
|
||||
|
||||
params
|
||||
Provides parameter mappings in the form of dictionary. Each key refers to a
|
||||
placeholder used in the ``template`` attribute.
|
||||
placeholder used in the ``template`` attribute. From HOT version
|
||||
``2015-10-15`` you may optionally pass non-string parameter values
|
||||
(e.g json/map/list parameters or attributes) and they will be serialized
|
||||
as json before replacing, prior heat/HOT versions require string values.
|
||||
|
||||
|
||||
The following example shows a simple use of the ``str_replace`` function in the
|
||||
outputs section of a template to build a URL for logging into a deployed
|
||||
|
@ -262,6 +262,57 @@ class Replace(cfn_funcs.Replace):
|
||||
return mapping, string
|
||||
|
||||
|
||||
class ReplaceJson(Replace):
|
||||
'''
|
||||
A function for performing string substitutions.
|
||||
|
||||
Behaves the same as Replace, but tolerates non-string parameter
|
||||
values, e.g map/list - these are serialized as json before doing
|
||||
the string substitution.
|
||||
'''
|
||||
|
||||
def result(self):
|
||||
template = function.resolve(self._string)
|
||||
mapping = function.resolve(self._mapping)
|
||||
|
||||
if not isinstance(template, six.string_types):
|
||||
raise TypeError(_('"%s" template must be a string') % self.fn_name)
|
||||
|
||||
if not isinstance(mapping, collections.Mapping):
|
||||
raise TypeError(_('"%s" params must be a map') % self.fn_name)
|
||||
|
||||
def replace(string, change):
|
||||
placeholder, value = change
|
||||
|
||||
if not isinstance(placeholder, six.string_types):
|
||||
raise TypeError(_('"%s" param placeholders must be strings') %
|
||||
self.fn_name)
|
||||
|
||||
if value is None:
|
||||
value = ''
|
||||
|
||||
if not isinstance(value,
|
||||
(six.string_types, six.integer_types,
|
||||
float, bool)):
|
||||
if isinstance(value,
|
||||
(collections.Mapping, collections.Sequence)):
|
||||
try:
|
||||
value = jsonutils.dumps(value, default=None)
|
||||
except TypeError:
|
||||
raise TypeError(_('"%(name)s" params must be strings, '
|
||||
'numbers, list or map. '
|
||||
'Failed to json serialize %(value)s'
|
||||
) % {'name': self.fn_name,
|
||||
'value': value})
|
||||
else:
|
||||
raise TypeError(_('"%s" params must be strings, numbers, '
|
||||
'list or map. ') % self.fn_name)
|
||||
|
||||
return string.replace(placeholder, six.text_type(value))
|
||||
|
||||
return six.moves.reduce(replace, six.iteritems(mapping), template)
|
||||
|
||||
|
||||
class GetFile(function.Function):
|
||||
"""
|
||||
A function for including a file inline.
|
||||
|
@ -335,7 +335,7 @@ class HOTemplate20151015(HOTemplate20150430):
|
||||
'list_join': hot_funcs.JoinMultiple,
|
||||
'repeat': hot_funcs.Repeat,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
'str_replace': hot_funcs.Replace,
|
||||
'str_replace': hot_funcs.ReplaceJson,
|
||||
|
||||
# functions added since 20150430
|
||||
'str_split': hot_funcs.StrSplit,
|
||||
|
@ -490,6 +490,26 @@ class HOTemplateTest(common.HeatTestCase):
|
||||
|
||||
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
|
||||
|
||||
def test_str_replace_map_param(self):
|
||||
"""Test str_replace function with non-string params."""
|
||||
|
||||
snippet = {'str_replace': {'template': 'jsonvar1',
|
||||
'params': {'jsonvar1': {'foo': 123}}}}
|
||||
snippet_resolved = '{"foo": 123}'
|
||||
|
||||
tmpl = template.Template(hot_liberty_tpl_empty)
|
||||
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
|
||||
|
||||
def test_str_replace_list_param(self):
|
||||
"""Test str_replace function with non-string params."""
|
||||
|
||||
snippet = {'str_replace': {'template': 'listvar1',
|
||||
'params': {'listvar1': ['foo', 123]}}}
|
||||
snippet_resolved = '["foo", 123]'
|
||||
|
||||
tmpl = template.Template(hot_liberty_tpl_empty)
|
||||
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
|
||||
|
||||
def test_str_replace_number(self):
|
||||
"""Test str_replace function with numbers."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user