Merge "Allows condition name using boolean or function"
This commit is contained in:
commit
9df7ee3a09
|
@ -644,7 +644,7 @@ the following syntax
|
||||||
update_policy: <update policy>
|
update_policy: <update policy>
|
||||||
deletion_policy: <deletion policy>
|
deletion_policy: <deletion policy>
|
||||||
external_id: <external resource ID>
|
external_id: <external resource ID>
|
||||||
condition: <condition name>
|
condition: <condition name or expression or boolean>
|
||||||
|
|
||||||
resource ID
|
resource ID
|
||||||
A resource ID which must be unique within the ``resources`` section of the
|
A resource ID which must be unique within the ``resources`` section of the
|
||||||
|
@ -775,7 +775,7 @@ according to the following syntax
|
||||||
<parameter name>:
|
<parameter name>:
|
||||||
description: <description>
|
description: <description>
|
||||||
value: <parameter value>
|
value: <parameter value>
|
||||||
condition: <condition name>
|
condition: <condition name or expression or boolean>
|
||||||
|
|
||||||
parameter name
|
parameter name
|
||||||
The output parameter name, which must be unique within the ``outputs``
|
The output parameter name, which must be unique within the ``outputs``
|
||||||
|
|
|
@ -11,11 +11,14 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.engine.cfn import functions as cfn_funcs
|
from heat.engine.cfn import functions as cfn_funcs
|
||||||
|
from heat.engine import function
|
||||||
from heat.engine import parameters
|
from heat.engine import parameters
|
||||||
from heat.engine import rsrc_defn
|
from heat.engine import rsrc_defn
|
||||||
from heat.engine import template_common
|
from heat.engine import template_common
|
||||||
|
@ -121,7 +124,7 @@ class CfnTemplateBase(template_common.CommonTemplate):
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
|
||||||
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
|
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
|
||||||
cond_name = defn.condition_name()
|
cond_name = defn.condition()
|
||||||
|
|
||||||
if cond_name is not None:
|
if cond_name is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -205,14 +208,14 @@ class CfnTemplate(CfnTemplateBase):
|
||||||
for arg in super(CfnTemplate, self)._rsrc_defn_args(stack, name, data):
|
for arg in super(CfnTemplate, self)._rsrc_defn_args(stack, name, data):
|
||||||
yield arg
|
yield arg
|
||||||
|
|
||||||
def no_parse(field, path):
|
parse_cond = functools.partial(self.parse_condition, stack)
|
||||||
return field
|
|
||||||
|
|
||||||
yield ('condition',
|
yield ('condition',
|
||||||
self._parse_resource_field(self.RES_CONDITION,
|
self._parse_resource_field(self.RES_CONDITION,
|
||||||
(six.string_types, bool),
|
(six.string_types, bool,
|
||||||
|
function.Function),
|
||||||
'string or boolean',
|
'string or boolean',
|
||||||
name, data, no_parse))
|
name, data, parse_cond))
|
||||||
|
|
||||||
|
|
||||||
class HeatTemplate(CfnTemplateBase):
|
class HeatTemplate(CfnTemplateBase):
|
||||||
|
|
|
@ -53,6 +53,9 @@ class Conditions(object):
|
||||||
if condition_name is None:
|
if condition_name is None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if isinstance(condition_name, bool):
|
||||||
|
return condition_name
|
||||||
|
|
||||||
if not (isinstance(condition_name, six.string_types) and
|
if not (isinstance(condition_name, six.string_types) and
|
||||||
condition_name in self._conditions):
|
condition_name in self._conditions):
|
||||||
raise ValueError(_('Invalid condition "%s"') % condition_name)
|
raise ValueError(_('Invalid condition "%s"') % condition_name)
|
||||||
|
|
|
@ -1130,17 +1130,22 @@ class If(function.Macro):
|
||||||
not isinstance(self.args, collections.Sequence) or
|
not isinstance(self.args, collections.Sequence) or
|
||||||
isinstance(self.args, six.string_types)):
|
isinstance(self.args, six.string_types)):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
cd_name, value_if_true, value_if_false = self.args
|
condition, value_if_true, value_if_false = self.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]')
|
||||||
raise ValueError(msg % self.fn_name)
|
raise ValueError(msg % self.fn_name)
|
||||||
|
|
||||||
cd = self._get_condition(cd_name)
|
cond = self.template.parse_condition(self.stack, condition,
|
||||||
|
self.fn_name)
|
||||||
|
cd = self._get_condition(function.resolve(cond))
|
||||||
return parse_func(value_if_true if cd else value_if_false)
|
return parse_func(value_if_true if cd else value_if_false)
|
||||||
|
|
||||||
def _get_condition(self, cd_name):
|
def _get_condition(self, cond):
|
||||||
return self.template.conditions(self.stack).is_enabled(cd_name)
|
if isinstance(cond, bool):
|
||||||
|
return cond
|
||||||
|
|
||||||
|
return self.template.conditions(self.stack).is_enabled(cond)
|
||||||
|
|
||||||
|
|
||||||
class ConditionBoolean(function.Function):
|
class ConditionBoolean(function.Function):
|
||||||
|
|
|
@ -241,7 +241,7 @@ class HOTemplate20130523(template_common.CommonTemplate):
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
|
||||||
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
|
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
|
||||||
cond_name = defn.condition_name()
|
cond_name = defn.condition()
|
||||||
|
|
||||||
if cond_name is not None:
|
if cond_name is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -494,9 +494,7 @@ class HOTemplate20161014(HOTemplate20160408):
|
||||||
yield arg
|
yield arg
|
||||||
|
|
||||||
parse = functools.partial(self.parse, stack)
|
parse = functools.partial(self.parse, stack)
|
||||||
|
parse_cond = functools.partial(self.parse_condition, stack)
|
||||||
def no_parse(field, path):
|
|
||||||
return field
|
|
||||||
|
|
||||||
yield ('external_id',
|
yield ('external_id',
|
||||||
self._parse_resource_field(self.RES_EXTERNAL_ID,
|
self._parse_resource_field(self.RES_EXTERNAL_ID,
|
||||||
|
@ -507,6 +505,7 @@ class HOTemplate20161014(HOTemplate20160408):
|
||||||
|
|
||||||
yield ('condition',
|
yield ('condition',
|
||||||
self._parse_resource_field(self.RES_CONDITION,
|
self._parse_resource_field(self.RES_CONDITION,
|
||||||
(six.string_types, bool),
|
(six.string_types, bool,
|
||||||
'string or boolean',
|
function.Function),
|
||||||
name, data, no_parse))
|
'string_or_boolean',
|
||||||
|
name, data, parse_cond))
|
||||||
|
|
|
@ -135,8 +135,9 @@ class ResourceDefinitionCore(object):
|
||||||
self._deletion_policy = self.RETAIN
|
self._deletion_policy = self.RETAIN
|
||||||
|
|
||||||
if condition is not None:
|
if condition is not None:
|
||||||
assert isinstance(condition, six.string_types)
|
assert isinstance(condition, (six.string_types, bool,
|
||||||
self._hash ^= hash(condition)
|
function.Function))
|
||||||
|
self._hash ^= _hash_data(condition)
|
||||||
|
|
||||||
def freeze(self, **overrides):
|
def freeze(self, **overrides):
|
||||||
"""Return a frozen resource definition, with all functions resolved.
|
"""Return a frozen resource definition, with all functions resolved.
|
||||||
|
@ -268,12 +269,12 @@ class ResourceDefinitionCore(object):
|
||||||
"""Return the external resource id."""
|
"""Return the external resource id."""
|
||||||
return function.resolve(self._external_id)
|
return function.resolve(self._external_id)
|
||||||
|
|
||||||
def condition_name(self):
|
def condition(self):
|
||||||
"""Return the name of the conditional inclusion rule, if any.
|
"""Return the name of the conditional inclusion rule, if any.
|
||||||
|
|
||||||
Returns None if the resource is included unconditionally.
|
Returns None if the resource is included unconditionally.
|
||||||
"""
|
"""
|
||||||
return self._condition
|
return function.resolve(self._condition)
|
||||||
|
|
||||||
def render_hot(self):
|
def render_hot(self):
|
||||||
"""Return a HOT snippet for the resource definition."""
|
"""Return a HOT snippet for the resource definition."""
|
||||||
|
|
|
@ -179,9 +179,12 @@ class CommonTemplate(template.Template):
|
||||||
description = val.get(self.OUTPUT_DESCRIPTION)
|
description = val.get(self.OUTPUT_DESCRIPTION)
|
||||||
|
|
||||||
if hasattr(self, 'OUTPUT_CONDITION'):
|
if hasattr(self, 'OUTPUT_CONDITION'):
|
||||||
cond_name = val.get(self.OUTPUT_CONDITION)
|
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
|
||||||
|
cond = self.parse_condition(stack,
|
||||||
|
val.get(self.OUTPUT_CONDITION),
|
||||||
|
'.'.join(path))
|
||||||
try:
|
try:
|
||||||
enabled = conds.is_enabled(cond_name)
|
enabled = conds.is_enabled(function.resolve(cond))
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
|
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
|
||||||
message = six.text_type(exc)
|
message = six.text_type(exc)
|
||||||
|
|
|
@ -1207,6 +1207,35 @@ class HOTemplateTest(common.HeatTestCase):
|
||||||
resolved = self.resolve(snippet, tmpl, stack)
|
resolved = self.resolve(snippet, tmpl, stack)
|
||||||
self.assertEqual('value_if_false', resolved)
|
self.assertEqual('value_if_false', resolved)
|
||||||
|
|
||||||
|
def test_if_using_boolean_condition(self):
|
||||||
|
snippet = {'if': [True, 'value_if_true', 'value_if_false']}
|
||||||
|
# when condition is true, if function resolve to value_if_true
|
||||||
|
tmpl = template.Template(hot_newton_tpl_empty)
|
||||||
|
stack = parser.Stack(utils.dummy_context(),
|
||||||
|
'test_if_using_boolean_condition', tmpl)
|
||||||
|
resolved = self.resolve(snippet, tmpl, stack)
|
||||||
|
self.assertEqual('value_if_true', resolved)
|
||||||
|
# when condition is false, if function resolve to value_if_false
|
||||||
|
snippet = {'if': [False, 'value_if_true', 'value_if_false']}
|
||||||
|
resolved = self.resolve(snippet, tmpl, stack)
|
||||||
|
self.assertEqual('value_if_false', resolved)
|
||||||
|
|
||||||
|
def test_if_using_condition_function(self):
|
||||||
|
tmpl_with_conditions = template_format.parse('''
|
||||||
|
heat_template_version: 2016-10-14
|
||||||
|
conditions:
|
||||||
|
create_prod: False
|
||||||
|
''')
|
||||||
|
snippet = {'if': [{'not': 'create_prod'},
|
||||||
|
'value_if_true', 'value_if_false']}
|
||||||
|
|
||||||
|
tmpl = template.Template(tmpl_with_conditions)
|
||||||
|
stack = parser.Stack(utils.dummy_context(),
|
||||||
|
'test_if_using_condition_function', tmpl)
|
||||||
|
|
||||||
|
resolved = self.resolve(snippet, tmpl, stack)
|
||||||
|
self.assertEqual('value_if_true', resolved)
|
||||||
|
|
||||||
def test_if_invalid_args(self):
|
def test_if_invalid_args(self):
|
||||||
snippet = {'if': ['create_prod', 'one_value']}
|
snippet = {'if': ['create_prod', 'one_value']}
|
||||||
tmpl = template.Template(hot_newton_tpl_empty)
|
tmpl = template.Template(hot_newton_tpl_empty)
|
||||||
|
|
|
@ -297,7 +297,7 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
||||||
'outputs': {
|
'outputs': {
|
||||||
'foo': {
|
'foo': {
|
||||||
'condition': 'prod_env',
|
'condition': 'prod_env',
|
||||||
'value': {'get_attr': ['r1', 'foo']}
|
'value': 'show me'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,6 +400,15 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
||||||
ex = self.assertRaises(ValueError, conds.is_enabled, 111)
|
ex = self.assertRaises(ValueError, conds.is_enabled, 111)
|
||||||
self.assertIn('Invalid condition "111"', six.text_type(ex))
|
self.assertIn('Invalid condition "111"', six.text_type(ex))
|
||||||
|
|
||||||
|
def test_res_condition_using_boolean(self):
|
||||||
|
tmpl = copy.deepcopy(self.tmpl)
|
||||||
|
# test condition name is boolean
|
||||||
|
stk = stack.Stack(self.ctx, 'test_res_cd_boolean', tmpl)
|
||||||
|
|
||||||
|
conds = tmpl.conditions(stk)
|
||||||
|
self.assertTrue(conds.is_enabled(True))
|
||||||
|
self.assertFalse(conds.is_enabled(False))
|
||||||
|
|
||||||
def test_parse_output_condition_invalid(self):
|
def test_parse_output_condition_invalid(self):
|
||||||
stk = stack.Stack(self.ctx,
|
stk = stack.Stack(self.ctx,
|
||||||
'test_output_invalid_condition',
|
'test_output_invalid_condition',
|
||||||
|
@ -441,6 +450,31 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
||||||
self.assertIn('Circular definition for condition "first_cond"',
|
self.assertIn('Circular definition for condition "first_cond"',
|
||||||
six.text_type(ex))
|
six.text_type(ex))
|
||||||
|
|
||||||
|
def test_parse_output_condition_boolean(self):
|
||||||
|
t = copy.deepcopy(self.tmpl.t)
|
||||||
|
t['outputs']['foo']['condition'] = True
|
||||||
|
stk = stack.Stack(self.ctx,
|
||||||
|
'test_output_cd_boolean',
|
||||||
|
template.Template(t))
|
||||||
|
|
||||||
|
self.assertEqual('show me', stk.outputs['foo'].get_value())
|
||||||
|
|
||||||
|
t = copy.deepcopy(self.tmpl.t)
|
||||||
|
t['outputs']['foo']['condition'] = False
|
||||||
|
stk = stack.Stack(self.ctx,
|
||||||
|
'test_output_cd_boolean',
|
||||||
|
template.Template(t))
|
||||||
|
self.assertIsNone(stk.outputs['foo'].get_value())
|
||||||
|
|
||||||
|
def test_parse_output_condition_function(self):
|
||||||
|
t = copy.deepcopy(self.tmpl.t)
|
||||||
|
t['outputs']['foo']['condition'] = {'not': 'prod_env'}
|
||||||
|
stk = stack.Stack(self.ctx,
|
||||||
|
'test_output_cd_function',
|
||||||
|
template.Template(t))
|
||||||
|
|
||||||
|
self.assertEqual('show me', stk.outputs['foo'].get_value())
|
||||||
|
|
||||||
|
|
||||||
class TestTemplateValidate(common.HeatTestCase):
|
class TestTemplateValidate(common.HeatTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue