Provide 'or' function
Provides condition function 'or' for templates: AWSTemplateFormatVersion.2010-09-09 heat_template_version.2016-10-14 Change-Id: I6888453404a2cba5127e5d89f7445c5dfe2b7a37 Blueprint: support-conditions-function
This commit is contained in:
parent
91355034fc
commit
bfd8d7b2ab
|
@ -448,3 +448,26 @@ Usage
|
|||
|
||||
Returns true if the param 'env_type' equals to 'prod' and the param 'zone' is
|
||||
not equal to 'beijing', otherwise returns false.
|
||||
|
||||
------
|
||||
Fn::Or
|
||||
------
|
||||
Acts as an OR operator to evaluate all the specified conditions.
|
||||
Returns true if any one of the specified conditions evaluate to true,
|
||||
or returns false if all of the conditions evaluates to false.
|
||||
|
||||
Parameters
|
||||
~~~~~~~~~~
|
||||
condition:
|
||||
A condition such as Fn::Equals that evaluates to true or false.
|
||||
|
||||
Usage
|
||||
~~~~~
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
{'Fn::Or': [{'Fn::Equals': [{'Ref': zone}, 'shanghai']},
|
||||
{'Fn::Equals': [{'Ref': zone}, 'beijing']}]}
|
||||
|
||||
Returns true if the param 'zone' equals to 'shanghai' or 'beijing',
|
||||
otherwise returns false.
|
||||
|
|
|
@ -244,13 +244,16 @@ for the ``heat_template_version`` key:
|
|||
This version adds ``equals`` condition function which can be used
|
||||
to compare whether two values are equal, the ``not`` condition function
|
||||
which acts as a NOT operator, the ``and`` condition function which acts
|
||||
as an AND operator to evaluate all the specified conditions. The complete
|
||||
list of supported condition functions is::
|
||||
as an AND operator to evaluate all the specified conditions, the ``or``
|
||||
condition function which acts as an OR operator to evaluate all the
|
||||
specified conditions. The complete list of supported condition
|
||||
functions is::
|
||||
|
||||
equals
|
||||
get_param
|
||||
not
|
||||
and
|
||||
or
|
||||
|
||||
.. _hot_spec_parameter_groups:
|
||||
|
||||
|
@ -836,6 +839,7 @@ expression
|
|||
get_param
|
||||
not
|
||||
and
|
||||
or
|
||||
|
||||
Note: In condition functions, you can reference a value from an input
|
||||
parameter, but you cannot reference resource or its attribute.
|
||||
|
@ -866,6 +870,14 @@ An example of conditions section definition
|
|||
equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
cd6:
|
||||
or:
|
||||
- equals:
|
||||
- get_param: zone
|
||||
- shanghai
|
||||
- equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
|
||||
The example below shows how to associate condition with resources
|
||||
|
||||
|
@ -1640,3 +1652,37 @@ For example
|
|||
|
||||
If param 'env_type' equals to 'prod', and param 'zone' is not equal to
|
||||
'beijing', this function returns true, otherwise returns false.
|
||||
|
||||
or
|
||||
--
|
||||
The ``or`` function acts as an OR operator to evaluate all the
|
||||
specified conditions.
|
||||
|
||||
The syntax of the ``or`` function is
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
or: [{condition_1}, {condition_2}, ... {condition_n}}]
|
||||
|
||||
Note: A condition such as ``equals`` or ``not`` that evaluates to true or
|
||||
false can be defined in ``or`` function, also we can set a boolean
|
||||
value as condition.
|
||||
|
||||
Returns true if any one of the specified conditions evaluate to true,
|
||||
or returns false if all of the conditions evaluates to false.
|
||||
|
||||
For example
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
or:
|
||||
- equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
- not:
|
||||
equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
|
||||
If param 'env_type' equals to 'prod', or the param 'zone' is not equal to
|
||||
'beijing', this function returns true, otherwise returns false.
|
||||
|
|
|
@ -201,7 +201,8 @@ class CfnTemplate(CfnTemplateBase):
|
|||
'Ref': cfn_funcs.ParamRef,
|
||||
'Fn::FindInMap': cfn_funcs.FindInMap,
|
||||
'Fn::Not': cfn_funcs.Not,
|
||||
'Fn::And': hot_funcs.And
|
||||
'Fn::And': hot_funcs.And,
|
||||
'Fn::Or': hot_funcs.Or
|
||||
}
|
||||
|
||||
def __init__(self, tmpl, template_id=None, files=None, env=None):
|
||||
|
|
|
@ -1037,3 +1037,39 @@ class And(function.Function):
|
|||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Or(function.Function):
|
||||
"""A function acts as an OR operator to evaluate all the conditions.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "or" : [{condition_1}, {condition_2}, {...}, {condition_n}] }
|
||||
|
||||
Returns true if any one of the specified conditions evaluate to true,
|
||||
or returns false if all of the conditions evaluates to false. The minimum
|
||||
number of conditions that you can include is 2.
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Or, self).__init__(stack, fn_name, args)
|
||||
if (not self.args or
|
||||
not (isinstance(self.args, collections.Sequence) and
|
||||
not isinstance(self.args, six.string_types)) or
|
||||
len(self.args) < 2):
|
||||
msg = _('Arguments to "%s" must be of the form: '
|
||||
'[{condition_1}, {condition_2}, {...}, {condition_n}], '
|
||||
'the minimum number of conditions is 2.')
|
||||
raise ValueError(msg % self.fn_name)
|
||||
|
||||
def result(self):
|
||||
for cd in self.args:
|
||||
resolved_value = function.resolve(cd)
|
||||
if not isinstance(resolved_value, bool):
|
||||
msg = _('The condition value should be boolean, '
|
||||
'after resolved the value is: %s')
|
||||
raise ValueError(msg % resolved_value)
|
||||
if resolved_value:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -466,7 +466,8 @@ class HOTemplate20161014(HOTemplate20160408):
|
|||
'get_param': hot_funcs.GetParam,
|
||||
'equals': hot_funcs.Equals,
|
||||
'not': hot_funcs.Not,
|
||||
'and': hot_funcs.And
|
||||
'and': hot_funcs.And,
|
||||
'or': hot_funcs.Or
|
||||
}
|
||||
|
||||
def __init__(self, tmpl, template_id=None, files=None, env=None):
|
||||
|
|
|
@ -1066,6 +1066,69 @@ class TemplateTest(common.HeatTestCase):
|
|||
'after resolved the value is: cd1')
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
|
||||
def test_or(self):
|
||||
tpl = template_format.parse('''
|
||||
AWSTemplateFormatVersion: 2010-09-09
|
||||
Parameters:
|
||||
zone:
|
||||
Type: String
|
||||
Default: 'guangzhou'
|
||||
''')
|
||||
snippet = {
|
||||
'Fn::Or': [
|
||||
{'Fn::Equals': [{'Ref': 'zone'}, 'shanghai']},
|
||||
{'Fn::Equals': [{'Ref': 'zone'}, 'beijing']}]}
|
||||
# when param 'zone' is neither equal to 'shanghai' nor 'beijing',
|
||||
# the 'or' function resolve to false
|
||||
tmpl = template.Template(tpl)
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_or_false', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertFalse(resolved)
|
||||
# when param 'zone' equals to 'shanghai' or 'beijing',
|
||||
# the 'or' function resolve to true
|
||||
tmpl = template.Template(tpl,
|
||||
env=environment.Environment(
|
||||
{'zone': 'beijing'}))
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_or_true', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertTrue(resolved)
|
||||
|
||||
tmpl = template.Template(tpl,
|
||||
env=environment.Environment(
|
||||
{'zone': 'shanghai'}))
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_or_true', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertTrue(resolved)
|
||||
|
||||
def test_or_invalid_args(self):
|
||||
tmpl = template.Template(aws_empty_template)
|
||||
|
||||
snippet = {'Fn::Or': ['invalid_arg']}
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve_condition, snippet, tmpl)
|
||||
|
||||
error_msg = ('.Fn::Or: Arguments to "Fn::Or" must be '
|
||||
'of the form: [{condition_1}, {condition_2}, {...}, '
|
||||
'{condition_n}]')
|
||||
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
# test invalid type
|
||||
snippet = {'Fn::Or': 'invalid'}
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve_condition, snippet, tmpl)
|
||||
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
|
||||
snippet = {'Fn::Or': ['cd1', True]}
|
||||
exc = self.assertRaises(ValueError,
|
||||
self.resolve_condition, snippet, tmpl)
|
||||
error_msg = ('The condition value should be boolean, '
|
||||
'after resolved the value is: cd1')
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
|
||||
def test_join(self):
|
||||
tmpl = template.Template(empty_template)
|
||||
join = {"Fn::Join": [" ", ["foo", "bar"]]}
|
||||
|
|
|
@ -38,6 +38,14 @@ Conditions:
|
|||
- Fn::Equals:
|
||||
- Ref: zone
|
||||
- beijing
|
||||
Fujian_Zone:
|
||||
Fn::Or:
|
||||
- Fn::Equals:
|
||||
- Ref: zone
|
||||
- fuzhou
|
||||
- Fn::Equals:
|
||||
- Ref: zone
|
||||
- xiamen
|
||||
Resources:
|
||||
test_res:
|
||||
Type: OS::Heat::TestResource
|
||||
|
@ -58,6 +66,11 @@ Resources:
|
|||
Properties:
|
||||
value: beijing_prod_res
|
||||
Condition: Beijing_Prod
|
||||
fujian_res:
|
||||
Type: OS::Heat::TestResource
|
||||
Condition: Fujian_Zone
|
||||
Properties:
|
||||
value: fujian_res
|
||||
Outputs:
|
||||
res_value:
|
||||
Value: {"Fn::GetAtt": [prod_res, output]}
|
||||
|
@ -99,6 +112,14 @@ conditions:
|
|||
- equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
fujian_zone:
|
||||
or:
|
||||
- equals:
|
||||
- get_param: zone
|
||||
- fuzhou
|
||||
- equals:
|
||||
- get_param: zone
|
||||
- xiamen
|
||||
resources:
|
||||
test_res:
|
||||
type: OS::Heat::TestResource
|
||||
|
@ -119,6 +140,11 @@ resources:
|
|||
properties:
|
||||
value: beijing_prod_res
|
||||
condition: beijing_prod
|
||||
fujian_res:
|
||||
type: OS::Heat::TestResource
|
||||
condition: fujian_zone
|
||||
properties:
|
||||
value: fujian_res
|
||||
outputs:
|
||||
res_value:
|
||||
value: {get_attr: [prod_res, output]}
|
||||
|
@ -140,19 +166,30 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
def setUp(self):
|
||||
super(CreateUpdateResConditionTest, self).setUp()
|
||||
|
||||
def res_assert_for_prod(self, resources, bj_prod=True):
|
||||
def res_assert_for_prod(self, resources, bj_prod=True, fj_zone=False):
|
||||
res_names = [res.resource_name for res in resources]
|
||||
if bj_prod:
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertIn('beijing_prod_res', res_names)
|
||||
elif fj_zone:
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertIn('fujian_res', res_names)
|
||||
self.assertNotIn('beijing_prod_res', res_names)
|
||||
else:
|
||||
self.assertEqual(2, len(resources))
|
||||
self.assertIn('prod_res', res_names)
|
||||
self.assertIn('test_res', res_names)
|
||||
|
||||
def res_assert_for_test(self, resources):
|
||||
self.assertEqual(2, len(resources))
|
||||
def res_assert_for_test(self, resources, fj_zone=False):
|
||||
res_names = [res.resource_name for res in resources]
|
||||
|
||||
if fj_zone:
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertIn('fujian_res', res_names)
|
||||
else:
|
||||
self.assertEqual(2, len(resources))
|
||||
self.assertNotIn('fujian_res', res_names)
|
||||
|
||||
self.assertIn('test_res', res_names)
|
||||
self.assertIn('test_res1', res_names)
|
||||
self.assertNotIn('prod_res', res_names)
|
||||
|
@ -210,6 +247,15 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_test(resources)
|
||||
self.output_assert_for_test(stack_identifier)
|
||||
|
||||
parms = {'zone': 'fuzhou'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_test(resources, fj_zone=True)
|
||||
self.output_assert_for_test(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'prod'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
|
@ -229,6 +275,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_prod(resources, False)
|
||||
self.output_assert_for_prod(stack_identifier, False)
|
||||
|
||||
parms = {'env_type': 'prod',
|
||||
'zone': 'xiamen'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
|
||||
self.output_assert_for_prod(stack_identifier, False)
|
||||
|
||||
def test_stack_create_update_cfn_template_prod_to_test(self):
|
||||
parms = {'env_type': 'prod'}
|
||||
stack_identifier = self.stack_create(template=cfn_template,
|
||||
|
@ -237,6 +293,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
parms = {'zone': 'xiamen',
|
||||
'env_type': 'prod'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
|
||||
self.output_assert_for_prod(stack_identifier, bj_prod=False)
|
||||
|
||||
parms = {'env_type': 'test'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
|
@ -246,6 +312,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_test(resources)
|
||||
self.output_assert_for_test(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'test',
|
||||
'zone': 'fuzhou'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_test(resources, fj_zone=True)
|
||||
self.output_assert_for_test(stack_identifier)
|
||||
|
||||
def test_stack_create_update_hot_template_test_to_prod(self):
|
||||
stack_identifier = self.stack_create(template=hot_template)
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
|
|
Loading…
Reference in New Issue