diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 22d97166f2..9cb39495f4 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -24,6 +24,7 @@ from heat.api.openstack.v1 import util from heat.common import identifier from heat.common import wsgi from heat.common import template_format +from heat.common import environment_format from heat.rpc import api as engine_api from heat.rpc import client as rpc_client from heat.common import urlfetch @@ -59,15 +60,17 @@ class InstantiationData(object): self.data = data @staticmethod - def format_parse(data, data_type, add_template_sections=True): + def format_parse(data, data_type): """ Parse the supplied data as JSON or YAML, raising the appropriate exception if it is in the wrong format. """ try: - return template_format.parse(data, - add_template_sections) + if data_type == 'Environment': + return environment_format.parse(data) + else: + return template_format.parse(data) except ValueError: err_reason = _("%s not in valid format") % data_type raise exc.HTTPBadRequest(err_reason) @@ -115,17 +118,9 @@ class InstantiationData(object): env = env_data else: env = self.format_parse(env_data, - 'Environment', - add_template_sections=False) - - for field in env: - if field not in ('parameters', 'resource_registry'): - reason = _("%s not in valid in the environment") % field - raise exc.HTTPBadRequest(reason) - - if not env.get(self.PARAM_USER_PARAMS): - env[self.PARAM_USER_PARAMS] = {} + 'Environment') + environment_format.default_for_missing(env) parameters = self.data.get(self.PARAM_USER_PARAMS, {}) env[self.PARAM_USER_PARAMS].update(parameters) return env diff --git a/heat/common/environment_format.py b/heat/common/environment_format.py new file mode 100644 index 0000000000..bec0f49ae3 --- /dev/null +++ b/heat/common/environment_format.py @@ -0,0 +1,50 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from heat.common.template_format import yaml + + +SECTIONS = (PARAMETERS, RESOURCE_REGISTRY) = \ + ('parameters', 'resource_registry') + + +def parse(env_str): + ''' + Takes a string and returns a dict containing the parsed structure. + This includes determination of whether the string is using the + JSON or YAML format. + ''' + try: + env = yaml.safe_load(env_str) + except (yaml.scanner.ScannerError, yaml.parser.ParserError) as e: + raise ValueError(e) + else: + if env is None: + env = {} + + for param in env: + if param not in SECTIONS: + raise ValueError('environment has wrong section "%s"' % param) + + return env + + +def default_for_missing(env): + ''' + Checks a parsed environment for missing sections. + ''' + for param in SECTIONS: + if param not in env: + env[param] = {} diff --git a/heat/common/template_format.py b/heat/common/template_format.py index d6bd8e814e..ac9b9675e2 100644 --- a/heat/common/template_format.py +++ b/heat/common/template_format.py @@ -37,7 +37,7 @@ yaml.SafeLoader.add_constructor(u'tag:yaml.org,2002:timestamp', _construct_yaml_str) -def parse(tmpl_str, add_template_sections=True): +def parse(tmpl_str): ''' Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the @@ -53,7 +53,7 @@ def parse(tmpl_str, add_template_sections=True): else: if tpl is None: tpl = {} - if add_template_sections and u'heat_template_version' not in tpl: + if u'heat_template_version' not in tpl: default_for_missing(tpl, u'HeatTemplateFormatVersion', HEAT_VERSIONS) return tpl diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index 3911e7fabd..8e89d24abb 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -142,7 +142,8 @@ blarg: wibble def test_parameters(self): params = {'foo': 'bar', 'blarg': 'wibble'} - body = {'parameters': params} + body = {'parameters': params, + 'resource_registry': {}} data = stacks.InstantiationData(body) self.assertEqual(data.environment(), body) @@ -156,7 +157,8 @@ blarg: wibble body = {'parameters': {'foo': 'bar'}, 'environment': {'parameters': {'blarg': 'wibble'}}} expect = {'parameters': {'blarg': 'wibble', - 'foo': 'bar'}} + 'foo': 'bar'}, + 'resource_registry': {}} data = stacks.InstantiationData(body) self.assertEqual(data.environment(), expect) @@ -169,12 +171,14 @@ blarg: wibble 'tester': 'fail'}}} expect = {'parameters': {'blarg': 'wibble', 'foo': 'bar', - 'tester': 'Yes'}} + 'tester': 'Yes'}, + 'resource_registry': {}} data = stacks.InstantiationData(body) self.assertEqual(data.environment(), expect) def test_environment_bad_format(self): - body = {'environment': {'somethingnotsupported': {'blarg': 'wibble'}}} + env = {'somethingnotsupported': {'blarg': 'wibble'}} + body = {'environment': json.dumps(env)} data = stacks.InstantiationData(body) self.assertRaises(webob.exc.HTTPBadRequest, data.environment) @@ -182,7 +186,9 @@ blarg: wibble env = {'foo': 'bar', 'blarg': 'wibble'} body = {'not the environment': env} data = stacks.InstantiationData(body) - self.assertEqual(data.environment(), {'parameters': {}}) + self.assertEqual(data.environment(), + {'parameters': {}, + 'resource_registry': {}}) def test_args(self): body = { @@ -443,7 +449,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': identity.stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -480,7 +487,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': identity.stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {'my.yaml': 'This is the file contents.'}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -517,7 +525,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -527,7 +536,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -537,7 +547,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -584,7 +595,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -618,7 +630,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'create_stack', 'args': {'stack_name': stack_name, 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -957,7 +970,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'update_stack', 'args': {'stack_identity': dict(identity), 'template': template, - 'params': {'parameters': parameters}, + 'params': {'parameters': parameters, + 'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, @@ -992,7 +1006,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): 'method': 'update_stack', 'args': {'stack_identity': dict(identity), 'template': template, - 'params': {u'parameters': parameters}, + 'params': {u'parameters': parameters, + u'resource_registry': {}}, 'files': {}, 'args': {'timeout_mins': 30}}, 'version': self.api_version}, diff --git a/heat/tests/test_environment_format.py b/heat/tests/test_environment_format.py new file mode 100644 index 0000000000..8a6e95738e --- /dev/null +++ b/heat/tests/test_environment_format.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from heat.common import environment_format +from heat.tests import common + + +class YamlEnvironmentTest(common.HeatTestCase): + + def test_minimal_yaml(self): + yaml1 = '' + yaml2 = ''' +parameters: {} +resource_registry: {} +''' + tpl1 = environment_format.parse(yaml1) + environment_format.default_for_missing(tpl1) + tpl2 = environment_format.parse(yaml2) + self.assertEqual(tpl1, tpl2) + + def test_wrong_sections(self): + env = ''' +parameters: {} +resource_regis: {} +''' + self.assertRaises(ValueError, environment_format.parse, env) + + def test_bad_yaml(self): + env = ''' +parameters: } +''' + self.assertRaises(ValueError, environment_format.parse, env) diff --git a/heat/tests/test_template_format.py b/heat/tests/test_template_format.py index 83843a1fd2..8bde30d600 100644 --- a/heat/tests/test_template_format.py +++ b/heat/tests/test_template_format.py @@ -90,24 +90,6 @@ Outputs: {} self.assertEqual(tpl1, tpl2) -class YamlEnvironmentTest(HeatTestCase): - - def test_no_template_sections(self): - env = ''' -parameters: {} -resource_registry: {} -''' - parsed_env = template_format.parse(env, add_template_sections=False) - - self.assertEqual('parameters' in parsed_env, True) - self.assertEqual('resource_registry' in parsed_env, True) - - self.assertEqual('Parameters' in parsed_env, False) - self.assertEqual('Mappings' in parsed_env, False) - self.assertEqual('Resources' in parsed_env, False) - self.assertEqual('Outputs' in parsed_env, False) - - class JsonYamlResolvedCompareTest(HeatTestCase): def setUp(self):