Provide 'and' function
Provides condition function 'and' for templates: AWSTemplateFormatVersion.2010-09-09 heat_template_version.2016-10-14 Blueprint: support-conditions-function Change-Id: I7a9a10b871f50a5a2885adc72d60d626f960557c
This commit is contained in:
parent
aeb15bf818
commit
6fa325e34e
|
@ -425,3 +425,26 @@ Usage
|
|||
|
||||
Returns false if the param 'env_type' equals to 'prod',
|
||||
otherwise returns true.
|
||||
|
||||
-------
|
||||
Fn::And
|
||||
-------
|
||||
Acts as an AND operator to evaluate all the specified conditions.
|
||||
Returns true if all the specified conditions evaluate to true, or returns
|
||||
false if any one 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::And': [{'Fn::Equals': [{'Ref': env_type}, 'prod']},
|
||||
{'Fn::Not': [{'Fn::Equals': [{'Ref': zone}, 'beijing']}]}]
|
||||
|
||||
Returns true if the param 'env_type' equals to 'prod' and the param 'zone' is
|
||||
not equal to 'beijing', otherwise returns false.
|
||||
|
|
|
@ -243,12 +243,14 @@ 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 complete list of supported
|
||||
condition functions is::
|
||||
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::
|
||||
|
||||
equals
|
||||
get_param
|
||||
not
|
||||
and
|
||||
|
||||
.. _hot_spec_parameter_groups:
|
||||
|
||||
|
@ -833,6 +835,7 @@ expression
|
|||
equals
|
||||
get_param
|
||||
not
|
||||
and
|
||||
|
||||
Note: In condition functions, you can reference a value from an input
|
||||
parameter, but you cannot reference resource or its attribute.
|
||||
|
@ -854,6 +857,15 @@ An example of conditions section definition
|
|||
equals:
|
||||
- get_param: param3
|
||||
- yes
|
||||
cd5:
|
||||
and:
|
||||
- equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
- not:
|
||||
equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
|
||||
The example below shows how to associate condition with resources
|
||||
|
||||
|
@ -1594,3 +1606,37 @@ Another example
|
|||
not: True
|
||||
|
||||
This function returns false.
|
||||
|
||||
and
|
||||
---
|
||||
The ``and`` function acts as an AND operator to evaluate all the
|
||||
specified conditions.
|
||||
|
||||
The syntax of the ``and`` function is
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
and: [{condition_1}, {condition_2}, ... {condition_n}}]
|
||||
|
||||
Note: A condition such as ``equals`` or ``not`` that evaluates to true or
|
||||
false can be defined in ``and`` function, also we can set a boolean
|
||||
value as condition.
|
||||
|
||||
Returns true if all the specified conditions evaluate to true, or returns
|
||||
false if any one of the conditions evaluates to false.
|
||||
|
||||
For example
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
and:
|
||||
- equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
- not:
|
||||
equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
|
||||
If param 'env_type' equals to 'prod', and param 'zone' is not equal to
|
||||
'beijing', this function returns true, otherwise returns false.
|
||||
|
|
|
@ -200,7 +200,8 @@ class CfnTemplate(CfnTemplateBase):
|
|||
'Fn::Equals': hot_funcs.Equals,
|
||||
'Ref': cfn_funcs.ParamRef,
|
||||
'Fn::FindInMap': cfn_funcs.FindInMap,
|
||||
'Fn::Not': cfn_funcs.Not
|
||||
'Fn::Not': cfn_funcs.Not,
|
||||
'Fn::And': hot_funcs.And
|
||||
}
|
||||
|
||||
def __init__(self, tmpl, template_id=None, files=None, env=None):
|
||||
|
|
|
@ -999,3 +999,39 @@ class Not(function.Function):
|
|||
'after resolved the value is: %s')
|
||||
raise ValueError(msg % resolved_value)
|
||||
return not resolved_value
|
||||
|
||||
|
||||
class And(function.Function):
|
||||
"""A function acts as an AND operator.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "and" : [{condition_1}, {condition_2}, {...}, {condition_n}] }
|
||||
|
||||
Returns true if all the specified conditions evaluate to true, or returns
|
||||
false if any one 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(And, 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 not resolved_value:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -465,7 +465,8 @@ class HOTemplate20161014(HOTemplate20160408):
|
|||
condition_functions = {
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'equals': hot_funcs.Equals,
|
||||
'not': hot_funcs.Not
|
||||
'not': hot_funcs.Not,
|
||||
'and': hot_funcs.And
|
||||
}
|
||||
|
||||
def __init__(self, tmpl, template_id=None, files=None, env=None):
|
||||
|
|
|
@ -999,6 +999,73 @@ class TemplateTest(common.HeatTestCase):
|
|||
'of the form: [condition]')
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
|
||||
def test_and(self):
|
||||
tpl = template_format.parse('''
|
||||
AWSTemplateFormatVersion: 2010-09-09
|
||||
Parameters:
|
||||
env_type:
|
||||
Type: String
|
||||
Default: 'test'
|
||||
zone:
|
||||
Type: String
|
||||
Default: 'shanghai'
|
||||
''')
|
||||
snippet = {
|
||||
'Fn::And': [
|
||||
{'Fn::Equals': [{'Ref': 'env_type'}, 'prod']},
|
||||
{'Fn::Not': [{'Fn::Equals': [{'Ref': 'zone'}, "beijing"]}]}]}
|
||||
# when param 'env_type' is 'test', and function resolve to false
|
||||
tmpl = template.Template(tpl)
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_and_false', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertFalse(resolved)
|
||||
# when param 'env_type' is 'prod', and param 'zone' is 'shanghai',
|
||||
# the 'and' function resolve to true
|
||||
tmpl = template.Template(tpl,
|
||||
env=environment.Environment(
|
||||
{'env_type': 'prod'}))
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_and_true', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertTrue(resolved)
|
||||
# when param 'env_type' is 'prod', and param 'zone' is 'shanghai',
|
||||
# the 'and' function resolve to true
|
||||
tmpl = template.Template(tpl,
|
||||
env=environment.Environment(
|
||||
{'env_type': 'prod',
|
||||
'zone': 'beijing'}))
|
||||
stk = stack.Stack(utils.dummy_context(),
|
||||
'test_and_false', tmpl)
|
||||
resolved = self.resolve_condition(snippet, tmpl, stk)
|
||||
self.assertFalse(resolved)
|
||||
|
||||
def test_and_invalid_args(self):
|
||||
tmpl = template.Template(aws_empty_template)
|
||||
|
||||
snippet = {'Fn::And': ['invalid_arg']}
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve_condition, snippet, tmpl)
|
||||
|
||||
error_msg = ('.Fn::And: Arguments to "Fn::And" must be '
|
||||
'of the form: [{condition_1}, {condition_2}, {...}, '
|
||||
'{condition_n}]')
|
||||
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
# test invalid type
|
||||
snippet = {'Fn::And': 'invalid'}
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve_condition, snippet, tmpl)
|
||||
|
||||
self.assertIn(error_msg, six.text_type(exc))
|
||||
|
||||
snippet = {'Fn::And': ['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"]]}
|
||||
|
|
|
@ -20,6 +20,9 @@ Parameters:
|
|||
Default: test
|
||||
Type: String
|
||||
AllowedValues: [prod, test]
|
||||
zone:
|
||||
Type: String
|
||||
Default: beijing
|
||||
Conditions:
|
||||
Prod: {"Fn::Equals" : [{Ref: env_type}, "prod"]}
|
||||
Test:
|
||||
|
@ -27,6 +30,14 @@ Conditions:
|
|||
- Fn::Equals:
|
||||
- Ref: env_type
|
||||
- prod
|
||||
Beijing_Prod:
|
||||
Fn::And:
|
||||
- Fn::Equals:
|
||||
- Ref: env_type
|
||||
- prod
|
||||
- Fn::Equals:
|
||||
- Ref: zone
|
||||
- beijing
|
||||
Resources:
|
||||
test_res:
|
||||
Type: OS::Heat::TestResource
|
||||
|
@ -42,6 +53,11 @@ Resources:
|
|||
Properties:
|
||||
value: just in test env
|
||||
Condition: Test
|
||||
beijing_prod_res:
|
||||
Type: OS::Heat::TestResource
|
||||
Properties:
|
||||
value: beijing_prod_res
|
||||
Condition: Beijing_Prod
|
||||
Outputs:
|
||||
res_value:
|
||||
Value: {"Fn::GetAtt": [prod_res, output]}
|
||||
|
@ -53,6 +69,8 @@ Outputs:
|
|||
test_res1_value:
|
||||
Value: {"Fn::If": [Test, {"Fn::GetAtt": [test_res1, output]},
|
||||
'no_test_res1']}
|
||||
beijing_prod_res:
|
||||
Value: {"Fn::If": [Beijing_Prod, {Ref: beijing_prod_res}, 'no_prod_res']}
|
||||
'''
|
||||
|
||||
hot_template = '''
|
||||
|
@ -63,6 +81,9 @@ parameters:
|
|||
type: string
|
||||
constraints:
|
||||
- allowed_values: [prod, test]
|
||||
zone:
|
||||
type: string
|
||||
default: beijing
|
||||
conditions:
|
||||
prod: {equals : [{get_param: env_type}, "prod"]}
|
||||
test:
|
||||
|
@ -70,6 +91,14 @@ conditions:
|
|||
equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
beijing_prod:
|
||||
and:
|
||||
- equals:
|
||||
- get_param: zone
|
||||
- beijing
|
||||
- equals:
|
||||
- get_param: env_type
|
||||
- prod
|
||||
resources:
|
||||
test_res:
|
||||
type: OS::Heat::TestResource
|
||||
|
@ -85,6 +114,11 @@ resources:
|
|||
properties:
|
||||
value: just in test env
|
||||
condition: test
|
||||
beijing_prod_res:
|
||||
type: OS::Heat::TestResource
|
||||
properties:
|
||||
value: beijing_prod_res
|
||||
condition: beijing_prod
|
||||
outputs:
|
||||
res_value:
|
||||
value: {get_attr: [prod_res, output]}
|
||||
|
@ -95,6 +129,9 @@ outputs:
|
|||
value: {if: [prod, {get_resource: prod_res}, 'no_prod_res']}
|
||||
test_res1_value:
|
||||
value: {if: [test, {get_attr: [test_res1, output]}, 'no_test_res1']}
|
||||
beijing_prod_res:
|
||||
value: {if: [beijing_prod, {get_resource: beijing_prod_res},
|
||||
'no_prod_res']}
|
||||
'''
|
||||
|
||||
|
||||
|
@ -103,9 +140,13 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
def setUp(self):
|
||||
super(CreateUpdateResConditionTest, self).setUp()
|
||||
|
||||
def res_assert_for_prod(self, resources):
|
||||
self.assertEqual(2, len(resources))
|
||||
def res_assert_for_prod(self, resources, bj_prod=True):
|
||||
res_names = [res.resource_name for res in resources]
|
||||
if bj_prod:
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertIn('beijing_prod_res', res_names)
|
||||
else:
|
||||
self.assertEqual(2, len(resources))
|
||||
self.assertIn('prod_res', res_names)
|
||||
self.assertIn('test_res', res_names)
|
||||
|
||||
|
@ -116,7 +157,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.assertIn('test_res1', res_names)
|
||||
self.assertNotIn('prod_res', res_names)
|
||||
|
||||
def output_assert_for_prod(self, stack_id):
|
||||
def output_assert_for_prod(self, stack_id, bj_prod=True):
|
||||
output = self.client.stacks.output_show(stack_id,
|
||||
'res_value')['output']
|
||||
self.assertEqual('prod_res', output['output_value'])
|
||||
|
@ -133,6 +174,14 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
stack_id, 'test_res1_value')['output']
|
||||
self.assertEqual('no_test_res1', test_res_output['output_value'])
|
||||
|
||||
beijing_prod_res = self.client.stacks.output_show(
|
||||
stack_id, 'beijing_prod_res')['output']
|
||||
if bj_prod:
|
||||
self.assertNotEqual('no_prod_res',
|
||||
beijing_prod_res['output_value'])
|
||||
else:
|
||||
self.assertEqual('no_prod_res', beijing_prod_res['output_value'])
|
||||
|
||||
def output_assert_for_test(self, stack_id):
|
||||
output = self.client.stacks.output_show(stack_id,
|
||||
'res_value')['output']
|
||||
|
@ -151,6 +200,10 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.assertEqual('just in test env',
|
||||
test_res_output['output_value'])
|
||||
|
||||
beijing_prod_res = self.client.stacks.output_show(
|
||||
stack_id, 'beijing_prod_res')['output']
|
||||
self.assertEqual('no_prod_res', beijing_prod_res['output_value'])
|
||||
|
||||
def test_stack_create_update_cfn_template_test_to_prod(self):
|
||||
stack_identifier = self.stack_create(template=cfn_template)
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
|
@ -166,6 +219,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'prod',
|
||||
'zone': 'shanghai'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=cfn_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources, False)
|
||||
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,
|
||||
|
@ -198,6 +261,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'prod',
|
||||
'zone': 'shanghai'}
|
||||
self.update_stack(stack_identifier,
|
||||
template=hot_template,
|
||||
parameters=parms)
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources, False)
|
||||
self.output_assert_for_prod(stack_identifier, False)
|
||||
|
||||
def test_stack_create_update_hot_template_prod_to_test(self):
|
||||
parms = {'env_type': 'prod'}
|
||||
stack_identifier = self.stack_create(template=hot_template,
|
||||
|
|
Loading…
Reference in New Issue