diff --git a/doc/source/template_guide/functions.rst b/doc/source/template_guide/functions.rst index 4d147ed6ee..2126871cac 100644 --- a/doc/source/template_guide/functions.rst +++ b/doc/source/template_guide/functions.rst @@ -338,3 +338,28 @@ Usage Returns ``{'key': 'door', 'colour': 'green'}``. + +---------- +Fn::Equals +---------- +Compares whether two values are equal. And returns true if the +two values are equal or false if they aren't. + +Parameters +~~~~~~~~~~ +value1: + A value of any type that you want to compare. + +value2: + A value of any type that you want to compare. + +Usage +~~~~~ + +.. code-block:: yaml + + {'Fn::Equals': [{'Ref': 'env_type'}, 'prod']} + + +Returns true if the param 'env_type' equals to 'prod', +otherwise returns false. diff --git a/doc/source/template_guide/hot_spec.rst b/doc/source/template_guide/hot_spec.rst index 113dc3de62..38031d39c2 100644 --- a/doc/source/template_guide/hot_spec.rst +++ b/doc/source/template_guide/hot_spec.rst @@ -187,7 +187,7 @@ For example, Heat currently supports the following values for the ---------- The key with value ``2016-04-08`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the - Mitaka release. This version also adds the map_merge function which + Mitaka release. This version also adds the ``map_merge`` function which can be used to merge the contents of maps. The complete list of supported functions is:: @@ -207,9 +207,10 @@ For example, Heat currently supports the following values for the ---------- The key with value ``2016-10-14`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the - Newton release. This version also adds the yaql function which - can be used for evaluation of complex expressions. The complete list of - supported functions is:: + Newton release. This version adds the ``yaql`` function which + can be used for evaluation of complex expressions, and also adds ``equals`` + function which can be used to compare whether two values are equal. + The complete list of supported functions is:: digest get_attr @@ -223,6 +224,7 @@ For example, Heat currently supports the following values for the str_replace str_split yaql + equals .. _hot_spec_parameter_groups: @@ -1315,3 +1317,25 @@ For example list_param: {get_param: list_param} max_elem output will be evaluated to 3 + +equals +------ +The ``equals`` function compares whether two values are equal. + +The syntax of the ``equals`` function is + +.. code-block:: yaml + + equals: [value_1, value_2] + +The value can be any type that you want to compare. This function +returns true if the two values are equal or false if they aren't. + +For example + +.. code-block:: yaml + + equals: [{get_param: env_type}, 'prod'] + +If param 'env_type' equals to 'prod', this function returns true, +otherwise returns false. diff --git a/heat/engine/cfn/functions.py b/heat/engine/cfn/functions.py index bd0f8125a0..f1f02299ab 100644 --- a/heat/engine/cfn/functions.py +++ b/heat/engine/cfn/functions.py @@ -48,6 +48,36 @@ class FindInMap(function.Function): return mapping[key][value] +class Equals(function.Function): + """A function for comparing whether two values are equal. + + Takes the form:: + + { "Fn::Equals" : ["value_1", "value_2"] } + + The value to be any type that you want to compare. Returns true + if the two values are equal or false if they aren't. + """ + + def __init__(self, stack, fn_name, args): + super(Equals, self).__init__(stack, fn_name, args) + try: + if (not self.args or + not isinstance(self.args, list)): + raise ValueError() + self.value1, self.value2 = self.args + except ValueError: + msg = _('Arguments to "%s" must be of the form: ' + '[value_1, value_2]') + raise ValueError(msg % self.fn_name) + + def result(self): + resolved_v1 = function.resolve(self.value1) + resolved_v2 = function.resolve(self.value2) + + return resolved_v1 == resolved_v2 + + class GetAZs(function.Function): """A function for retrieving the availability zones. diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py index edeb543d18..e579532915 100644 --- a/heat/engine/cfn/template.py +++ b/heat/engine/cfn/template.py @@ -214,3 +214,21 @@ class HeatTemplate(CfnTemplate): 'Fn::MemberListToMap': cfn_funcs.MemberListToMap, 'Fn::ResourceFacade': cfn_funcs.ResourceFacade, } + + +class HeatTemplate20161014(HeatTemplate): + functions = { + 'Fn::FindInMap': cfn_funcs.FindInMap, + 'Fn::GetAZs': cfn_funcs.GetAZs, + 'Ref': cfn_funcs.Ref, + 'Fn::GetAtt': cfn_funcs.GetAtt, + 'Fn::Select': cfn_funcs.Select, + 'Fn::Join': cfn_funcs.Join, + 'Fn::Split': cfn_funcs.Split, + 'Fn::Replace': cfn_funcs.Replace, + 'Fn::Base64': cfn_funcs.Base64, + 'Fn::MemberListToMap': cfn_funcs.MemberListToMap, + 'Fn::ResourceFacade': cfn_funcs.ResourceFacade, + # supports Fn::Equals in Newton + 'Fn::Equals': cfn_funcs.Equals, + } diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index f9a952f362..01a8ce9c18 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -414,6 +414,9 @@ class HOTemplate20161014(HOTemplate20160408): # functions added since 20161014 'yaql': hot_funcs.Yaql, + # functions added since 20161014 + 'equals': cfn_funcs.Equals, + # functions added since 20151015 'map_merge': hot_funcs.MapMerge, diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index 7004dca178..6362c85370 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -891,6 +891,43 @@ class HOTemplateTest(common.HeatTestCase): self.assertEqual(2, resolved) + def test_equals(self): + hot_tpl = template_format.parse(''' + heat_template_version: 2016-10-14 + parameters: + env_type: + type: string + default: 'test' + ''') + snippet = {'equals': [{'get_param': 'env_type'}, 'prod']} + # when param 'env_type' is 'test', equals function resolve to false + tmpl = template.Template(hot_tpl) + stack = parser.Stack(utils.dummy_context(), + 'test_equals_false', tmpl) + resolved = self.resolve(snippet, tmpl, stack) + self.assertFalse(resolved) + # when param 'env_type' is 'prod', equals function resolve to true + tmpl = template.Template(hot_tpl, + env=environment.Environment( + {'env_type': 'prod'})) + stack = parser.Stack(utils.dummy_context(), + 'test_equals_true', tmpl) + resolved = self.resolve(snippet, tmpl, stack) + self.assertTrue(resolved) + + def test_equals_invalid_args(self): + tmpl = template.Template(hot_newton_tpl_empty) + + snippet = {'equals': ['test', 'prod', 'invalid']} + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('Arguments to "equals" must be of the form: ' + '[value_1, value_2]', six.text_type(exc)) + + snippet = {'equals': "invalid condition"} + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('Arguments to "equals" must be of the form: ' + '[value_1, value_2]', six.text_type(exc)) + def test_repeat(self): """Test repeat function.""" snippet = {'repeat': {'template': 'this is %var%', diff --git a/heat/tests/test_template.py b/heat/tests/test_template.py index 8eb6bb3873..9d5cd756fb 100644 --- a/heat/tests/test_template.py +++ b/heat/tests/test_template.py @@ -55,6 +55,10 @@ empty_template = template_format.parse('''{ "HeatTemplateFormatVersion" : "2012-12-12", }''') +empty_template20161014 = template_format.parse('''{ + "HeatTemplateFormatVersion" : "2016-10-14", +}''') + parameter_template = template_format.parse('''{ "HeatTemplateFormatVersion" : "2012-12-12", "Parameters" : { @@ -523,7 +527,8 @@ class TemplateTest(common.HeatTestCase): invalid_heat_version_tmp) ex_error_msg = ('The template version is invalid: ' '"HeatTemplateFormatVersion: 2010-09-09". ' - '"HeatTemplateFormatVersion" should be: 2012-12-12') + '"HeatTemplateFormatVersion" should be one of: ' + '2012-12-12, 2016-10-14') self.assertEqual(ex_error_msg, six.text_type(init_ex)) def test_invalid_version_not_in_heat_versions(self): @@ -697,6 +702,43 @@ class TemplateTest(common.HeatTestCase): data = {"Fn::Select": ["one", '']} self.assertEqual("", self.resolve(data, tmpl)) + def test_equals(self): + tpl = template_format.parse(''' + HeatTemplateFormatVersion: 2016-10-14 + Parameters: + env_type: + Type: String + Default: 'test' + ''') + snippet = {'Fn::Equals': [{'Ref': 'env_type'}, 'prod']} + # when param 'env_type' is 'test', equals function resolve to false + tmpl = template.Template(tpl) + stk = stack.Stack(utils.dummy_context(), + 'test_equals_false', tmpl) + resolved = self.resolve(snippet, tmpl, stk) + self.assertFalse(resolved) + # when param 'env_type' is 'prod', equals function resolve to true + tmpl = template.Template(tpl, + env=environment.Environment( + {'env_type': 'prod'})) + stk = stack.Stack(utils.dummy_context(), + 'test_equals_true', tmpl) + resolved = self.resolve(snippet, tmpl, stk) + self.assertTrue(resolved) + + def test_equals_invalid_args(self): + tmpl = template.Template(empty_template20161014) + + snippet = {'Fn::Equals': ['test', 'prod', 'invalid']} + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('Arguments to "Fn::Equals" must be of the form: ' + '[value_1, value_2]', six.text_type(exc)) + # test invalid type + snippet = {'Fn::Equals': {"equal": False}} + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('Arguments to "Fn::Equals" must be of the form: ' + '[value_1, value_2]', six.text_type(exc)) + def test_join(self): tmpl = template.Template(empty_template) join = {"Fn::Join": [" ", ["foo", "bar"]]} diff --git a/setup.cfg b/setup.cfg index 67c442c9d9..c5eb7b94c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -145,6 +145,7 @@ heat.templates = heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408 heat_template_version.2016-10-14 = heat.engine.hot.template:HOTemplate20161014 HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:HeatTemplate + HeatTemplateFormatVersion.2016-10-14 = heat.engine.cfn.template:HeatTemplate20161014 AWSTemplateFormatVersion.2010-09-09 = heat.engine.cfn.template:CfnTemplate # These are for backwards compat with Icehouse notification_driver configuration values