Modify 'if' Macro to allow optional properties
Change-Id: I931d88e79fc077d12fc9bd39009061ffe87f1262 Story: 2007388 Task: 38973
This commit is contained in:
parent
674a62ae9b
commit
71a9c3d690
@ -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
|
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
|
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
|
digest
|
||||||
filter
|
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
|
in the property values in the ``resources`` section and ``outputs`` sections
|
||||||
of a template.
|
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
|
not
|
||||||
---
|
---
|
||||||
The ``not`` function acts as a NOT operator.
|
The ``not`` function acts as a NOT operator.
|
||||||
|
@ -1275,13 +1275,16 @@ class If(function.Macro):
|
|||||||
evaluates to false.
|
evaluates to false.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _read_args(self):
|
||||||
|
return self.args
|
||||||
|
|
||||||
def parse_args(self, parse_func):
|
def parse_args(self, parse_func):
|
||||||
try:
|
try:
|
||||||
if (not self.args or
|
if (not self.args or
|
||||||
not isinstance(self.args, collections.Sequence) or
|
not isinstance(self.args, collections.Sequence) or
|
||||||
isinstance(self.args, str)):
|
isinstance(self.args, str)):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
condition, value_if_true, value_if_false = self.args
|
condition, value_if_true, value_if_false = self._read_args()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
msg = _('Arguments to "%s" must be of the form: '
|
msg = _('Arguments to "%s" must be of the form: '
|
||||||
'[condition_name, value_if_true, value_if_false]')
|
'[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)
|
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):
|
class ConditionBoolean(function.Function):
|
||||||
"""Abstract parent class of boolean condition functions."""
|
"""Abstract parent class of boolean condition functions."""
|
||||||
|
|
||||||
|
@ -783,7 +783,8 @@ class HOTemplate20210416(HOTemplate20180831):
|
|||||||
# functions added in 2016-10-14
|
# functions added in 2016-10-14
|
||||||
'yaql': hot_funcs.Yaql,
|
'yaql': hot_funcs.Yaql,
|
||||||
'map_replace': hot_funcs.MapReplace,
|
'map_replace': hot_funcs.MapReplace,
|
||||||
'if': hot_funcs.If,
|
# Modified in 2021-04-16
|
||||||
|
'if': hot_funcs.IfNullable,
|
||||||
|
|
||||||
# functions added in 2017-02-24
|
# functions added in 2017-02-24
|
||||||
'filter': hot_funcs.Filter,
|
'filter': hot_funcs.Filter,
|
||||||
|
@ -71,6 +71,10 @@ hot_pike_tpl_empty = template_format.parse('''
|
|||||||
heat_template_version: 2017-09-01
|
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('''
|
hot_tpl_empty_sections = template_format.parse('''
|
||||||
heat_template_version: 2013-05-23
|
heat_template_version: 2013-05-23
|
||||||
parameters:
|
parameters:
|
||||||
@ -1506,13 +1510,42 @@ resources:
|
|||||||
self.assertEqual('', self.stack['AResource'].properties['Foo'])
|
self.assertEqual('', self.stack['AResource'].properties['Foo'])
|
||||||
|
|
||||||
def test_if_invalid_args(self):
|
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)
|
tmpl = template.Template(hot_newton_tpl_empty)
|
||||||
exc = self.assertRaises(exception.StackValidationFailed,
|
for snippet in snippets:
|
||||||
self.resolve, snippet, tmpl)
|
exc = self.assertRaises(exception.StackValidationFailed,
|
||||||
self.assertIn('Arguments to "if" must be of the form: '
|
self.resolve, snippet, tmpl)
|
||||||
'[condition_name, value_if_true, value_if_false]',
|
self.assertIn('Arguments to "if" must be of the form: '
|
||||||
str(exc))
|
'[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):
|
def test_if_condition_name_non_existing(self):
|
||||||
snippet = {'if': ['cd_not_existing', 'value_true', 'value_false']}
|
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