Add new hot function str_replace_strict
In many cases, a user would rather see an error result if a str_replace param is not substituted in the template, rather than the function silently doing nothing. Since str_replace is set in its ways, introduce a new function str_replace_strict which behaves identically to str_replace except that a ValueError is raised if any of the param's are not found in the template. Change-Id: I8b8c69bb49dfeb74e05af4871602c20493b081eb
This commit is contained in:
parent
3b0902a8c0
commit
aac3e7aae6
@ -259,7 +259,9 @@ for the ``heat_template_version`` key:
|
||||
-------------------
|
||||
The key with value ``2017-02-24`` or ``ocata`` indicates that the YAML
|
||||
document is a HOT template and it may contain features added and/or removed
|
||||
up until the Ocata release. The complete list of supported functions is::
|
||||
up until the Ocata release. This version adds the ``str_replace_strict``
|
||||
function which raises errors for missing params. The complete list of
|
||||
supported functions is::
|
||||
|
||||
digest
|
||||
get_attr
|
||||
@ -272,6 +274,7 @@ for the ``heat_template_version`` key:
|
||||
repeat
|
||||
resource_facade
|
||||
str_replace
|
||||
str_replace_strict
|
||||
str_split
|
||||
yaql
|
||||
if
|
||||
@ -1426,6 +1429,14 @@ provided parameter. The script for doing this is provided as userdata to the
|
||||
compute instance, leveraging the ``str_replace`` function.
|
||||
|
||||
|
||||
str_replace_strict
|
||||
------------------
|
||||
``str_replace_strict`` behaves identically to the ``str_replace``
|
||||
function, only an error is raised if any of the params are not present
|
||||
in the template. This may help catch typo's or other issues sooner
|
||||
rather than later when processing a template.
|
||||
|
||||
|
||||
str_split
|
||||
---------
|
||||
The ``str_split`` function allows for splitting a string into a list by
|
||||
|
@ -355,6 +355,8 @@ class Replace(function.Function):
|
||||
of equal length, lexicographically smaller keys are preferred.
|
||||
"""
|
||||
|
||||
_strict = False
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Replace, self).__init__(stack, fn_name, args)
|
||||
|
||||
@ -399,6 +401,9 @@ class Replace(function.Function):
|
||||
template = function.resolve(self._string)
|
||||
mapping = function.resolve(self._mapping)
|
||||
|
||||
if self._strict:
|
||||
unreplaced_keys = set(mapping)
|
||||
|
||||
if not isinstance(template, six.string_types):
|
||||
raise TypeError(_('"%s" template must be a string') % self.fn_name)
|
||||
|
||||
@ -416,11 +421,24 @@ class Replace(function.Function):
|
||||
|
||||
remaining_keys = keys[1:]
|
||||
value = self._validate_replacement(mapping[placeholder])
|
||||
return [value.join(replace(s.split(placeholder),
|
||||
|
||||
def string_split(s):
|
||||
ss = s.split(placeholder)
|
||||
if self._strict and len(ss) > 1:
|
||||
unreplaced_keys.discard(placeholder)
|
||||
return ss
|
||||
|
||||
return [value.join(replace(string_split(s),
|
||||
remaining_keys)) for s in strings]
|
||||
|
||||
return replace([template], sorted(sorted(mapping),
|
||||
key=len, reverse=True))[0]
|
||||
ret_val = replace([template], sorted(sorted(mapping),
|
||||
key=len, reverse=True))[0]
|
||||
if self._strict and len(unreplaced_keys) > 0:
|
||||
raise ValueError(
|
||||
_("The following params were not found in the template: %s") %
|
||||
','.join(sorted(sorted(unreplaced_keys),
|
||||
key=len, reverse=True)))
|
||||
return ret_val
|
||||
|
||||
|
||||
class ReplaceJson(Replace):
|
||||
@ -468,6 +486,17 @@ class ReplaceJson(Replace):
|
||||
return six.text_type(value)
|
||||
|
||||
|
||||
class ReplaceJsonStrict(ReplaceJson):
|
||||
"""A function for performing string substituions.
|
||||
|
||||
str_replace_strict is identical to the str_replace function, only
|
||||
a ValueError is raised if any of the params are not present in
|
||||
the template.
|
||||
"""
|
||||
|
||||
_strict = True
|
||||
|
||||
|
||||
class GetFile(function.Function):
|
||||
"""A function for including a file inline.
|
||||
|
||||
|
@ -536,6 +536,9 @@ class HOTemplate20170224(HOTemplate20161014):
|
||||
'map_replace': hot_funcs.MapReplace,
|
||||
'if': hot_funcs.If,
|
||||
|
||||
# functions added in 2017-02-24
|
||||
'str_replace_strict': hot_funcs.ReplaceJsonStrict,
|
||||
|
||||
# functions removed from 2015-10-15
|
||||
'Fn::Select': hot_funcs.Removed,
|
||||
|
||||
|
@ -64,6 +64,10 @@ hot_newton_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2016-10-14
|
||||
''')
|
||||
|
||||
hot_ocata_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2017-02-24
|
||||
''')
|
||||
|
||||
hot_tpl_empty_sections = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
parameters:
|
||||
@ -682,6 +686,65 @@ class HOTemplateTest(common.HeatTestCase):
|
||||
self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
|
||||
def test_str_replace_missing_param(self):
|
||||
"""Test str_replace function missing param is OK."""
|
||||
|
||||
snippet = {'str_replace':
|
||||
{'template': 'Template var1 string var2',
|
||||
'params': {'var1': 'foo', 'var2': 'bar',
|
||||
'var3': 'zed'}}}
|
||||
snippet_resolved = 'Template foo string bar'
|
||||
|
||||
# older template uses Replace, newer templates use ReplaceJson.
|
||||
# test both.
|
||||
for hot_tpl in (hot_tpl_empty, hot_ocata_tpl_empty):
|
||||
tmpl = template.Template(hot_tpl)
|
||||
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
|
||||
|
||||
def test_str_replace_strict_no_missing_param(self):
|
||||
"""Test str_replace_strict function no missing params, no problem."""
|
||||
|
||||
snippet = {'str_replace_strict':
|
||||
{'template': 'Template var1 var1 s var2 t varvarvar3',
|
||||
'params': {'var1': 'foo', 'var2': 'bar',
|
||||
'var3': 'zed', 'var': 'tricky '}}}
|
||||
snippet_resolved = 'Template foo foo s bar t tricky tricky zed'
|
||||
|
||||
tmpl = template.Template(hot_ocata_tpl_empty)
|
||||
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
|
||||
|
||||
def test_str_replace_strict_missing_param(self):
|
||||
"""Test str_replace_strict function missing param (s)raises error."""
|
||||
|
||||
snippet = {'str_replace_strict':
|
||||
{'template': 'Template var1 string var2',
|
||||
'params': {'var1': 'foo', 'var2': 'bar',
|
||||
'var3': 'zed'}}}
|
||||
|
||||
tmpl = template.Template(hot_ocata_tpl_empty)
|
||||
ex = self.assertRaises(ValueError, self.resolve, snippet, tmpl)
|
||||
self.assertEqual('The following params were not found in the '
|
||||
'template: var3', six.text_type(ex))
|
||||
|
||||
snippet = {'str_replace_strict':
|
||||
{'template': 'Template var1 string var2',
|
||||
'params': {'var1': 'foo', 'var2': 'bar',
|
||||
'var0': 'zed'}}}
|
||||
|
||||
ex = self.assertRaises(ValueError, self.resolve, snippet, tmpl)
|
||||
self.assertEqual('The following params were not found in the '
|
||||
'template: var0', six.text_type(ex))
|
||||
|
||||
snippet = {'str_replace_strict':
|
||||
{'template': 'Template var1 string var2',
|
||||
'params': {'var1': 'foo', 'var2': 'bar',
|
||||
'var0': 'zed', 'var': 'z',
|
||||
'longvarname': 'q'}}}
|
||||
|
||||
ex = self.assertRaises(ValueError, self.resolve, snippet, tmpl)
|
||||
self.assertEqual('The following params were not found in the '
|
||||
'template: longvarname,var0,var', six.text_type(ex))
|
||||
|
||||
def test_str_replace_invalid_param_keys(self):
|
||||
"""Test str_replace function parameter keys.
|
||||
|
||||
@ -705,6 +768,27 @@ class HOTemplateTest(common.HeatTestCase):
|
||||
self.assertIn('"str_replace" syntax should be str_replace:\\n',
|
||||
six.text_type(ex))
|
||||
|
||||
def test_str_replace_strict_invalid_param_keys(self):
|
||||
"""Test str_replace function parameter keys.
|
||||
|
||||
Pass wrong parameters to function and verify that we get
|
||||
a KeyError.
|
||||
"""
|
||||
|
||||
snippets = [{'str_replace_strict':
|
||||
{'t': 'Template var1 string var2',
|
||||
'params': {'var1': 'foo', 'var2': 'bar'}}},
|
||||
{'str_replace_strict':
|
||||
{'template': 'Template var1 string var2',
|
||||
'param': {'var1': 'foo', 'var2': 'bar'}}}]
|
||||
|
||||
for snippet in snippets:
|
||||
tmpl = template.Template(hot_ocata_tpl_empty)
|
||||
ex = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
self.assertIn('"str_replace_strict" syntax should be '
|
||||
'str_replace_strict:\\n', six.text_type(ex))
|
||||
|
||||
def test_str_replace_invalid_param_types(self):
|
||||
"""Test str_replace function parameter values.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user