Merge "Add 'contains' function"
This commit is contained in:
commit
c63d3cdd3b
|
@ -296,8 +296,9 @@ for the ``heat_template_version`` key:
|
||||||
up until the Pike release. This version adds the ``make_url`` function for
|
up until the Pike release. This version adds the ``make_url`` function for
|
||||||
assembling URLs, the ``list_concat`` function for combining multiple
|
assembling URLs, the ``list_concat`` function for combining multiple
|
||||||
lists, the ``list_concat_unique`` function for combining multiple
|
lists, the ``list_concat_unique`` function for combining multiple
|
||||||
lists without repeating items, and the``string_replace_vstrict`` which
|
lists without repeating items, the``string_replace_vstrict`` which
|
||||||
raises errors for missing and empty params. The complete list of
|
raises errors for missing and empty params, and the ``contains`` which
|
||||||
|
checks whether specific value is in a sequence. The complete list of
|
||||||
supported functions is::
|
supported functions is::
|
||||||
|
|
||||||
digest
|
digest
|
||||||
|
@ -310,6 +311,7 @@ for the ``heat_template_version`` key:
|
||||||
make_url
|
make_url
|
||||||
list_concat
|
list_concat
|
||||||
list_concat_unique
|
list_concat_unique
|
||||||
|
contains
|
||||||
map_merge
|
map_merge
|
||||||
map_replace
|
map_replace
|
||||||
repeat
|
repeat
|
||||||
|
@ -321,7 +323,7 @@ for the ``heat_template_version`` key:
|
||||||
yaql
|
yaql
|
||||||
if
|
if
|
||||||
|
|
||||||
We support 'yaql' as condition function in this version.
|
We support 'yaql' and 'contains' as condition functions in this version.
|
||||||
The complete list of supported condition functions is::
|
The complete list of supported condition functions is::
|
||||||
|
|
||||||
equals
|
equals
|
||||||
|
@ -330,6 +332,7 @@ for the ``heat_template_version`` key:
|
||||||
and
|
and
|
||||||
or
|
or
|
||||||
yaql
|
yaql
|
||||||
|
contains
|
||||||
|
|
||||||
.. _hot_spec_parameter_groups:
|
.. _hot_spec_parameter_groups:
|
||||||
|
|
||||||
|
@ -1958,3 +1961,25 @@ For example
|
||||||
list_concat_unique: [['v1', 'v2'], ['v2', 'v43']]
|
list_concat_unique: [['v1', 'v2'], ['v2', 'v43']]
|
||||||
|
|
||||||
Will resolve to the list ``['v1', 'v2', 'v3']``.
|
Will resolve to the list ``['v1', 'v2', 'v3']``.
|
||||||
|
|
||||||
|
contains
|
||||||
|
--------
|
||||||
|
|
||||||
|
The ``contains`` function checks whether the specific value is
|
||||||
|
in a sequence.
|
||||||
|
|
||||||
|
The syntax of the ``contains`` function is
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
contains: [<value>, <sequence>]
|
||||||
|
|
||||||
|
This function returns true if value is in sequence or false if it isn't.
|
||||||
|
|
||||||
|
For example
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
contains: ['v1', ['v1', 'v2', 'v3']]
|
||||||
|
|
||||||
|
Will resolve to boolean true.
|
||||||
|
|
|
@ -1527,3 +1527,43 @@ class ListConcatUnique(ListConcat):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_unique = True
|
_unique = True
|
||||||
|
|
||||||
|
|
||||||
|
class Contains(function.Function):
|
||||||
|
"""A function for checking whether specific value is in sequence.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
contains:
|
||||||
|
- <value>
|
||||||
|
- <sequence>
|
||||||
|
|
||||||
|
The value can be any type that you want to check. Returns true
|
||||||
|
if the specific value is in the sequence, otherwise returns false.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, stack, fn_name, args):
|
||||||
|
super(Contains, self).__init__(stack, fn_name, args)
|
||||||
|
example = '"%s" : [ "value1", [ "value1", "value2"]]' % self.fn_name
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'example': example}
|
||||||
|
|
||||||
|
if not self.args or not isinstance(self.args, list):
|
||||||
|
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
||||||
|
'should be: %(example)s') % fmt_data)
|
||||||
|
try:
|
||||||
|
self.value, self.sequence = self.args
|
||||||
|
except ValueError:
|
||||||
|
msg = _('Arguments to "%s" must be of the form: '
|
||||||
|
'[value1, [value1, value2]]')
|
||||||
|
raise ValueError(msg % self.fn_name)
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
resolved_value = function.resolve(self.value)
|
||||||
|
resolved_sequence = function.resolve(self.sequence)
|
||||||
|
|
||||||
|
if not isinstance(resolved_sequence, collections.Sequence):
|
||||||
|
raise TypeError(_('Second argument to "%s" should be '
|
||||||
|
'a sequence.') % self.fn_name)
|
||||||
|
|
||||||
|
return resolved_value in resolved_sequence
|
||||||
|
|
|
@ -593,6 +593,7 @@ class HOTemplate20170901(HOTemplate20170224):
|
||||||
'list_concat': hot_funcs.ListConcat,
|
'list_concat': hot_funcs.ListConcat,
|
||||||
'str_replace_vstrict': hot_funcs.ReplaceJsonVeryStrict,
|
'str_replace_vstrict': hot_funcs.ReplaceJsonVeryStrict,
|
||||||
'list_concat_unique': hot_funcs.ListConcatUnique,
|
'list_concat_unique': hot_funcs.ListConcatUnique,
|
||||||
|
'contains': hot_funcs.Contains,
|
||||||
|
|
||||||
# functions removed from 2015-10-15
|
# functions removed from 2015-10-15
|
||||||
'Fn::Select': hot_funcs.Removed,
|
'Fn::Select': hot_funcs.Removed,
|
||||||
|
@ -616,5 +617,6 @@ class HOTemplate20170901(HOTemplate20170224):
|
||||||
'or': hot_funcs.Or,
|
'or': hot_funcs.Or,
|
||||||
|
|
||||||
# functions added in 2017-09-01
|
# functions added in 2017-09-01
|
||||||
'yaql': hot_funcs.Yaql
|
'yaql': hot_funcs.Yaql,
|
||||||
|
'contains': hot_funcs.Contains
|
||||||
}
|
}
|
||||||
|
|
|
@ -2201,6 +2201,42 @@ conditions:
|
||||||
snippet = {'list_concat': ['v1', 'v2']}
|
snippet = {'list_concat': ['v1', 'v2']}
|
||||||
self._test_list_concat_invalid(snippet)
|
self._test_list_concat_invalid(snippet)
|
||||||
|
|
||||||
|
def test_contains_with_list(self):
|
||||||
|
snippet = {'contains': ['v1', ['v1', 'v2']]}
|
||||||
|
tmpl = template.Template(hot_pike_tpl_empty)
|
||||||
|
resolved = self.resolve(snippet, tmpl)
|
||||||
|
self.assertTrue(resolved)
|
||||||
|
|
||||||
|
def test_contains_with_string(self):
|
||||||
|
snippet = {'contains': ['a', 'abc']}
|
||||||
|
tmpl = template.Template(hot_pike_tpl_empty)
|
||||||
|
resolved = self.resolve(snippet, tmpl)
|
||||||
|
self.assertTrue(resolved)
|
||||||
|
|
||||||
|
def test_contains_with_invalid_args_type(self):
|
||||||
|
snippet = {'contains': {'key': 'value'}}
|
||||||
|
tmpl = template.Template(hot_pike_tpl_empty)
|
||||||
|
exc = self.assertRaises(exception.StackValidationFailed,
|
||||||
|
self.resolve, snippet, tmpl)
|
||||||
|
msg = 'Incorrect arguments to '
|
||||||
|
self.assertIn(msg, six.text_type(exc))
|
||||||
|
|
||||||
|
def test_contains_with_invalid_args_number(self):
|
||||||
|
snippet = {'contains': ['v1', ['v1', 'v2'], 'redundant']}
|
||||||
|
tmpl = template.Template(hot_pike_tpl_empty)
|
||||||
|
exc = self.assertRaises(exception.StackValidationFailed,
|
||||||
|
self.resolve, snippet, tmpl)
|
||||||
|
msg = 'must be of the form: [value1, [value1, value2]]'
|
||||||
|
self.assertIn(msg, six.text_type(exc))
|
||||||
|
|
||||||
|
def test_contains_with_invalid_sequence(self):
|
||||||
|
snippet = {'contains': ['v1', {'key': 'value'}]}
|
||||||
|
tmpl = template.Template(hot_pike_tpl_empty)
|
||||||
|
exc = self.assertRaises(TypeError,
|
||||||
|
self.resolve, snippet, tmpl)
|
||||||
|
msg = 'should be a sequence'
|
||||||
|
self.assertIn(msg, six.text_type(exc))
|
||||||
|
|
||||||
|
|
||||||
class HotStackTest(common.HeatTestCase):
|
class HotStackTest(common.HeatTestCase):
|
||||||
"""Test stack function when stack was created from HOT template."""
|
"""Test stack function when stack was created from HOT template."""
|
||||||
|
|
Loading…
Reference in New Issue