]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Add Fn::Select template function
authorAngus Salkeld <asalkeld@redhat.com>
Tue, 4 Jun 2013 23:37:19 +0000 (09:37 +1000)
committerAngus Salkeld <asalkeld@redhat.com>
Tue, 4 Jun 2013 23:37:50 +0000 (09:37 +1000)
It also knows how to lookup keys in dict's too.
The strings can be a serialized json blob too.

blueprint fn-select
blueprint fn-mapselect
Change-Id: Ie40afa47c1f0a699abb852bc22388f3f675172e6

heat/engine/parser.py
heat/engine/template.py
heat/tests/test_parser.py

index d0dabaff88046e16fa627a410e146cde060ca7be..6902443bad738a9f765171a76381ad1f686558c3 100644 (file)
@@ -573,6 +573,7 @@ def resolve_runtime_data(template, resources, snippet):
                                         resources=resources),
                       functools.partial(template.resolve_attributes,
                                         resources=resources),
+                      template.resolve_select,
                       template.resolve_joins,
                       template.resolve_base64])
 
index 3bd77f314d6c6b6ab43f1360db3d2d362a4d39c1..369dcb42bb50560df5f56bb87ac1e469fd819de9 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 
 import collections
+import json
 
 from heat.db import api as db_api
 from heat.common import exception
@@ -198,6 +199,50 @@ class Template(collections.Mapping):
 
         return _resolve(lambda k, v: k == 'Fn::Join', handle_join, s)
 
+    @staticmethod
+    def resolve_select(s):
+        '''
+        Resolve constructs of the form:
+        (for a list lookup)
+        { "Fn::Select" : [ "2", [ "apples", "grapes", "mangoes" ] ] }
+        returns "mangoes"
+
+        (for a dict lookup)
+        { "Fn::Select" : [ "red", {"red": "a", "flu": "b"} ] }
+        returns "a"
+
+        Note: can raise IndexError, KeyError, ValueError and TypeError
+        '''
+        def handle_select(args):
+            if not isinstance(args, (list, tuple)):
+                raise TypeError('Arguments to "Fn::Select" must be a list')
+
+            try:
+                lookup, strings = args
+            except ValueError as ex:
+                example = '"Fn::Select" : [ "4", [ "str1", "str2"]]'
+                raise ValueError('Incorrect arguments to "Fn::Select" %s: %s' %
+                                ('should be', example))
+
+            try:
+                index = int(lookup)
+            except ValueError as ex:
+                index = lookup
+
+            if isinstance(strings, basestring):
+                # might be serialized json.
+                # if not allow it to raise a ValueError
+                strings = json.loads(strings)
+
+            if isinstance(strings, (list, tuple)) and isinstance(index, int):
+                return strings[index]
+            if isinstance(strings, dict) and isinstance(index, basestring):
+                return strings[index]
+
+            raise TypeError('Arguments to "Fn::Select" not fully resolved')
+
+        return _resolve(lambda k, v: k == 'Fn::Select', handle_select, s)
+
     @staticmethod
     def resolve_joins(s):
         '''
index 5baa4c0f277068fd9f513f700c5005088464eb39..8bed4460e597526fd8befd2072cf7831303e049c 100644 (file)
@@ -12,8 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import json
 import mox
-
 import time
 import uuid
 
@@ -204,6 +204,61 @@ class TemplateTest(HeatTestCase):
                                                                resources),
                          p_snippet)
 
+    def test_select_from_list(self):
+        data = {"Fn::Select": ["1", ["foo", "bar"]]}
+        self.assertEqual(parser.Template.resolve_select(data), "bar")
+
+    def test_select_from_list_not_int(self):
+        data = {"Fn::Select": ["one", ["foo", "bar"]]}
+        self.assertRaises(TypeError, parser.Template.resolve_select,
+                          data)
+
+    def test_select_from_list_out_of_bound(self):
+        data = {"Fn::Select": ["3", ["foo", "bar"]]}
+        self.assertRaises(IndexError, parser.Template.resolve_select,
+                          data)
+
+    def test_select_from_dict(self):
+        data = {"Fn::Select": ["red", {"red": "robin", "re": "foo"}]}
+        self.assertEqual(parser.Template.resolve_select(data), "robin")
+
+    def test_select_from_dict_not_str(self):
+        data = {"Fn::Select": ["1", {"red": "robin", "re": "foo"}]}
+        self.assertRaises(TypeError, parser.Template.resolve_select,
+                          data)
+
+    def test_select_from_dict_not_existing(self):
+        data = {"Fn::Select": ["green", {"red": "robin", "re": "foo"}]}
+        self.assertRaises(KeyError, parser.Template.resolve_select,
+                          data)
+
+    def test_select_from_serialized_json_map(self):
+        js = json.dumps({"red": "robin", "re": "foo"})
+        data = {"Fn::Select": ["re", js]}
+        self.assertEqual(parser.Template.resolve_select(data), "foo")
+
+    def test_select_from_serialized_json_list(self):
+        js = json.dumps(["foo", "fee", "fum"])
+        data = {"Fn::Select": ["0", js]}
+        self.assertEqual(parser.Template.resolve_select(data), "foo")
+
+    def test_select_from_serialized_json_wrong(self):
+        js = "this is really not serialized json"
+        data = {"Fn::Select": ["not", js]}
+        self.assertRaises(ValueError, parser.Template.resolve_select,
+                          data)
+
+    def test_select_wrong_num_args(self):
+        join0 = {"Fn::Select": []}
+        self.assertRaises(ValueError, parser.Template.resolve_select,
+                          join0)
+        join1 = {"Fn::Select": ["4"]}
+        self.assertRaises(ValueError, parser.Template.resolve_select,
+                          join1)
+        join3 = {"Fn::Select": ["foo", {"foo": "bar"}, ""]}
+        self.assertRaises(ValueError, parser.Template.resolve_select,
+                          join3)
+
     def test_join_reduce(self):
         join = {"Fn::Join": [" ", ["foo", "bar", "baz", {'Ref': 'baz'},
                 "bink", "bonk"]]}