Merge "Add new hot function str_replace_strict"

This commit is contained in:
Jenkins 2016-10-17 10:52:27 +00:00 committed by Gerrit Code Review
commit 3a062fdf22
4 changed files with 131 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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