Merge "Modify 'if' Macro to allow optional properties"
This commit is contained in:
commit
5eec3e4bd0
@ -414,7 +414,15 @@ The complete list of supported condition functions is::
|
||||
--------------------
|
||||
The key with value ``2021-04-16`` or ``wallaby`` indicates that the YAML
|
||||
document is a HOT template and it may contain features added and/or removed
|
||||
up until the Wallaby release. The complete list of supported functions is::
|
||||
up until the Wallaby release.
|
||||
|
||||
This version adds a 2-argument variant of the ``if`` function. When the
|
||||
condition is false and no third argument is supplied, the entire enclosing item
|
||||
(which may be e.g. a list item, a key-value pair in a dict, or a property
|
||||
value) will be elided. This allows for e.g. conditional definition of
|
||||
properties while keeping the default value when the condition is false.
|
||||
|
||||
The complete list of supported functions is::
|
||||
|
||||
digest
|
||||
filter
|
||||
@ -1875,6 +1883,27 @@ template except for ``if`` conditions. You can use the ``if`` condition
|
||||
in the property values in the ``resources`` section and ``outputs`` sections
|
||||
of a template.
|
||||
|
||||
Beginning with the ``wallaby`` template version, the third argument is
|
||||
optional. If only two arguments are passed, the entire enclosing item is
|
||||
removed when the condition is false.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
conditions:
|
||||
override_name: {not: {equals: [{get_param: server_name}, ""]}}
|
||||
|
||||
resources:
|
||||
test_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
name: {if: [override_name, {get_param: server_name}]}
|
||||
|
||||
In this example, the default name for the server (which is generated by Heat
|
||||
when the property value is not specified) would be used when the
|
||||
``server_name`` parameter value is an empty string.
|
||||
|
||||
not
|
||||
---
|
||||
The ``not`` function acts as a NOT operator.
|
||||
|
@ -1275,13 +1275,16 @@ class If(function.Macro):
|
||||
evaluates to false.
|
||||
"""
|
||||
|
||||
def _read_args(self):
|
||||
return self.args
|
||||
|
||||
def parse_args(self, parse_func):
|
||||
try:
|
||||
if (not self.args or
|
||||
not isinstance(self.args, collections.Sequence) or
|
||||
isinstance(self.args, str)):
|
||||
raise ValueError()
|
||||
condition, value_if_true, value_if_false = self.args
|
||||
condition, value_if_true, value_if_false = self._read_args()
|
||||
except ValueError:
|
||||
msg = _('Arguments to "%s" must be of the form: '
|
||||
'[condition_name, value_if_true, value_if_false]')
|
||||
@ -1299,6 +1302,40 @@ class If(function.Macro):
|
||||
return self.template.conditions(self.stack).is_enabled(cond)
|
||||
|
||||
|
||||
class IfNullable(If):
|
||||
"""A function to return corresponding value based on condition evaluation.
|
||||
|
||||
Takes the form::
|
||||
|
||||
if:
|
||||
- <condition_name>
|
||||
- <value_if_true>
|
||||
- <value_if_false>
|
||||
|
||||
The value_if_true to be returned if the specified condition evaluates
|
||||
to true, the value_if_false to be returned if the specified condition
|
||||
evaluates to false.
|
||||
|
||||
If the value_if_false is omitted and the condition is false, the enclosing
|
||||
item (list item, dictionary key/value pair, property definition) will be
|
||||
treated as if it were not mentioned in the template::
|
||||
|
||||
if:
|
||||
- <condition_name>
|
||||
- <value_if_true>
|
||||
"""
|
||||
|
||||
def _read_args(self):
|
||||
if not (2 <= len(self.args) <= 3):
|
||||
raise ValueError()
|
||||
|
||||
if len(self.args) == 2:
|
||||
condition, value_if_true = self.args
|
||||
return condition, value_if_true, Ellipsis
|
||||
|
||||
return self.args
|
||||
|
||||
|
||||
class ConditionBoolean(function.Function):
|
||||
"""Abstract parent class of boolean condition functions."""
|
||||
|
||||
|
@ -783,7 +783,8 @@ class HOTemplate20210416(HOTemplate20180831):
|
||||
# functions added in 2016-10-14
|
||||
'yaql': hot_funcs.Yaql,
|
||||
'map_replace': hot_funcs.MapReplace,
|
||||
'if': hot_funcs.If,
|
||||
# Modified in 2021-04-16
|
||||
'if': hot_funcs.IfNullable,
|
||||
|
||||
# functions added in 2017-02-24
|
||||
'filter': hot_funcs.Filter,
|
||||
|
@ -71,6 +71,10 @@ hot_pike_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2017-09-01
|
||||
''')
|
||||
|
||||
hot_wallaby_tpl_empty = template_format.parse('''
|
||||
heat_template_version: 2021-04-16
|
||||
''')
|
||||
|
||||
hot_tpl_empty_sections = template_format.parse('''
|
||||
heat_template_version: 2013-05-23
|
||||
parameters:
|
||||
@ -1506,13 +1510,42 @@ resources:
|
||||
self.assertEqual('', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
def test_if_invalid_args(self):
|
||||
snippet = {'if': ['create_prod', 'one_value']}
|
||||
snippets = [
|
||||
{'if': ['create_prod', 'one_value']},
|
||||
{'if': ['create_prod', 'one_value', 'two_values', 'three_values']},
|
||||
]
|
||||
tmpl = template.Template(hot_newton_tpl_empty)
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
self.assertIn('Arguments to "if" must be of the form: '
|
||||
'[condition_name, value_if_true, value_if_false]',
|
||||
str(exc))
|
||||
for snippet in snippets:
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
self.assertIn('Arguments to "if" must be of the form: '
|
||||
'[condition_name, value_if_true, value_if_false]',
|
||||
str(exc))
|
||||
|
||||
def test_if_nullable_invalid_args(self):
|
||||
snippets = [
|
||||
{'if': ['create_prod']},
|
||||
{'if': ['create_prod', 'one_value', 'two_values', 'three_values']},
|
||||
]
|
||||
tmpl = template.Template(hot_wallaby_tpl_empty)
|
||||
for snippet in snippets:
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
self.assertIn('Arguments to "if" must be of the form: '
|
||||
'[condition_name, value_if_true, value_if_false]',
|
||||
str(exc))
|
||||
|
||||
def test_if_nullable(self):
|
||||
snippet = {
|
||||
'single': {'if': [False, 'value_if_true']},
|
||||
'nested_true': {'if': [True, {'if': [False, 'foo']}, 'bar']},
|
||||
'nested_false': {'if': [False, 'baz', {'if': [False, 'quux']}]},
|
||||
'control': {'if': [False, True, None]},
|
||||
}
|
||||
|
||||
tmpl = template.Template(hot_wallaby_tpl_empty)
|
||||
resolved = self.resolve(snippet, tmpl, None)
|
||||
self.assertEqual({'control': None}, resolved)
|
||||
|
||||
def test_if_condition_name_non_existing(self):
|
||||
snippet = {'if': ['cd_not_existing', 'value_true', 'value_false']}
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The ``wallaby`` template version introduces a new 2-argument form of the
|
||||
``if`` function. This allows users to specify optional property values, so
|
||||
that when the condition is false Heat treats it the same as if no value
|
||||
were specified for the property at all. The behaviour of existing templates
|
||||
is unchanged, even after updating the template version to ``wallaby``.
|
Loading…
Reference in New Issue
Block a user