diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index 7722b0f441..7c657efe7d 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -646,6 +646,12 @@ engine_status: in: body required: true type: string +Environment: + description: | + Environment for the stack, where multiple environment files are provided this will be the merged result. + in: body + required: false + type: object environment: description: | A JSON environment for the stack. @@ -943,7 +949,7 @@ ParameterGroups: type: array Parameters: description: | - Key and value pairs that contain CFN template parameters. + Parameter schema in CFN format. in: body required: true type: object diff --git a/api-ref/source/v1/samples/template-validate-response.json b/api-ref/source/v1/samples/template-validate-response.json index cbdf7c26f1..80c9374ab6 100644 --- a/api-ref/source/v1/samples/template-validate-response.json +++ b/api-ref/source/v1/samples/template-validate-response.json @@ -37,5 +37,13 @@ "param_name-2" ] } - ] + ], + "Environment": { + "event_sinks": [], + "parameter_defaults": {}, + "parameters": {}, + "resource_registry": { + "resources": {} + } + } } diff --git a/api-ref/source/v1/stack-templates.inc b/api-ref/source/v1/stack-templates.inc index e9b735871a..f6e0b42e98 100644 --- a/api-ref/source/v1/stack-templates.inc +++ b/api-ref/source/v1/stack-templates.inc @@ -145,6 +145,7 @@ Response Parameters - Description: Description - ParameterGroups: ParameterGroups - Parameters: Parameters + - Environment: Environment Response Example ---------------- diff --git a/heat/engine/service.py b/heat/engine/service.py index 5c6e2eb41e..fb94cb6b62 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -1310,6 +1310,7 @@ class EngineService(service.ServiceBase): result.update(nested_params(stack)) + result['Environment'] = tmpl.env.user_env_as_dict() return result @context.request_context diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index 128e093f32..49e8e000d9 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -880,6 +880,10 @@ parameters: type: string description: Name of private network to be created + merged_param: + type: comma_delimited_list + description: A merged list of values + resources: private_net: type: OS::Neutron::Net @@ -939,6 +943,11 @@ class ValidateTest(common.HeatTestCase): self.mock_is_service_available = self.mock_isa.start() self.addCleanup(self.mock_isa.stop) self.engine = service.EngineService('a', 't') + self.empty_environment = { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': {'resources': {}}} def _mock_get_image_id_success(self, imageId): self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id', @@ -1043,7 +1052,8 @@ class ValidateTest(common.HeatTestCase): other_template = test_template_no_default.replace( 'net_name', 'net_name2') - files = {'env1': 'parameter_defaults:\n net_name: net1', + files = {'env1': 'parameter_defaults:' + '\n net_name: net1', 'env2': 'parameter_defaults:' '\n net_name: net2' '\n net_name2: net3', @@ -1051,12 +1061,72 @@ class ValidateTest(common.HeatTestCase): 'tmpl2.yaml': other_template} params = {'parameters': {}, 'parameter_defaults': {}} - self.engine.validate_template( + ret = self.engine.validate_template( self.ctx, t, params=params, files=files, environment_files=['env1', 'env2']) self.assertEqual('net2', params['parameter_defaults']['net_name']) self.assertEqual('net3', params['parameter_defaults']['net_name2']) + expected = { + 'Description': 'No description', + 'Parameters': { + 'size': {'AllowedValues': [1, 4, 8], + 'Description': '', + 'Label': u'size', + 'NoEcho': 'false', + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': { + 'net_name': u'net2', + 'net_name2': u'net3'}, + 'parameters': {}, + 'resource_registry': {'resources': {}}}} + + self.assertEqual(expected, ret) + + def test_validate_parameters_merged_env(self): + t = template_format.parse(test_template_allowed_integers) + + other_template = test_template_no_default.replace( + 'net_name', 'net_name2') + + files = {'env1': 'parameter_defaults:' + '\n net_name: net1' + '\n merged_param: [net1, net2]' + '\nparameter_merge_strategies:' + '\n merged_param: merge', + 'env2': 'parameter_defaults:' + '\n net_name: net2' + '\n net_name2: net3' + '\n merged_param: [net3, net4]' + '\nparameter_merge_strategies:' + '\n merged_param: merge', + 'tmpl1.yaml': test_template_no_default, + 'tmpl2.yaml': other_template} + params = {'parameters': {}, 'parameter_defaults': {}} + + expected = { + 'Description': 'No description', + 'Parameters': { + 'size': {'AllowedValues': [1, 4, 8], + 'Description': '', + 'Label': u'size', + 'NoEcho': 'false', + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': { + 'net_name': u'net2', + 'net_name2': u'net3', + 'merged_param': ['net1', 'net2', 'net3', 'net4']}, + 'parameters': {}, + 'resource_registry': {'resources': {}}}} + ret = self.engine.validate_template( + self.ctx, t, + params=params, + files=files, environment_files=['env1', 'env2']) + self.assertEqual(expected, ret) def test_validate_hot_empty_parameters_valid(self): t = template_format.parse( @@ -1149,7 +1219,8 @@ class ValidateTest(common.HeatTestCase): res = dict(self.engine.validate_template(self.ctx, t, {})) expected = {"Description": "test.", - "Parameters": {}} + "Parameters": {}, + "Environment": self.empty_environment} self.assertEqual(expected, res) def test_validate_hot_empty_outputs_valid(self): @@ -1162,7 +1233,8 @@ class ValidateTest(common.HeatTestCase): res = dict(self.engine.validate_template(self.ctx, t, {})) expected = {"Description": "test.", - "Parameters": {}} + "Parameters": {}, + "Environment": self.empty_environment} self.assertEqual(expected, res) def test_validate_properties(self): @@ -1239,7 +1311,9 @@ class ValidateTest(common.HeatTestCase): t = template_format.parse(test_template_volume_snapshot) res = dict(self.engine.validate_template(self.ctx, t, {})) - self.assertEqual({'Description': u'test.', 'Parameters': {}}, res) + expected = {'Description': u'test.', 'Parameters': {}, + 'Environment': self.empty_environment} + self.assertEqual(expected, res) def test_validate_template_without_resources(self): hot_tpl = template_format.parse(''' @@ -1247,7 +1321,8 @@ class ValidateTest(common.HeatTestCase): ''') res = dict(self.engine.validate_template(self.ctx, hot_tpl, {})) - expected = {'Description': 'No description', 'Parameters': {}} + expected = {'Description': 'No description', 'Parameters': {}, + 'Environment': self.empty_environment} self.assertEqual(expected, res) def test_validate_template_with_invalid_resource_type(self): @@ -1755,7 +1830,8 @@ class ValidateTest(common.HeatTestCase): t, {}, ignorable_errors=[exception.ResourceTypeUnavailable.error_code])) - expected = {'Description': 'No description', 'Parameters': {}} + expected = {'Description': 'No description', 'Parameters': {}, + 'Environment': self.empty_environment} self.assertEqual(expected, res) def test_validate_with_ignorable_errors_invalid_error_code(self): @@ -1835,7 +1911,14 @@ parameter_groups: 'NoEcho': 'false', 'Type': 'String'}}, 'Type': 'OS::Test::TestResource'}}, - } + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': { + 'OS::Test::TestResource': + 'https://server.test/nested.template', + 'resources': {}}}} self.assertEqual(expected, res) def test_validate_allowed_external_rsrc(self): diff --git a/heat_integrationtests/functional/test_template_validate.py b/heat_integrationtests/functional/test_template_validate.py index e62c31b764..451de49c59 100644 --- a/heat_integrationtests/functional/test_template_validate.py +++ b/heat_integrationtests/functional/test_template_validate.py @@ -94,7 +94,12 @@ resources: 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', - 'Type': 'Number'}}} + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': {u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_override_default(self): @@ -108,7 +113,12 @@ resources: 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', - 'Type': 'Number'}}} + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {'aparam': 5}, + 'resource_registry': {u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_override_none(self): @@ -122,7 +132,14 @@ resources: 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', - 'Type': 'Number'}}} + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': { + 'OS::Heat::RandomString': 'OS::Heat::None', + u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_basic_required_param(self): @@ -133,7 +150,12 @@ resources: 'aparam': {'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', - 'Type': 'Number'}}} + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': {u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_fail_version(self): @@ -168,7 +190,12 @@ resources: 'Description': '', 'Label': 'cparam', 'NoEcho': 'true', - 'Type': 'String'}}} + 'Type': 'String'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': {u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_nested_off(self): @@ -181,7 +208,14 @@ resources: 'Description': 'the param description', 'Label': 'pparam', 'NoEcho': 'false', - 'Type': 'Number'}}} + 'Type': 'Number'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': { + u'mynested.yaml': u'mynested.yaml', + u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_nested_on(self): @@ -200,7 +234,14 @@ resources: 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}, - 'Type': 'mynested.yaml'}}} + 'Type': 'mynested.yaml'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': { + u'mynested.yaml': u'mynested.yaml', + u'resources': {}}}} self.assertEqual(expected, ret) def test_template_validate_nested_on_multiple(self): @@ -240,5 +281,12 @@ resources: 'NoEcho': 'false', 'Type': 'Number'}}, 'NestedParameters': n_param2, - 'Type': 'mynested.yaml'}}} + 'Type': 'mynested.yaml'}}, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': { + u'mynested.yaml': u'mynested.yaml', + 'resources': {}}}} self.assertEqual(expected, ret) diff --git a/heat_integrationtests/functional/test_unicode_template.py b/heat_integrationtests/functional/test_unicode_template.py index 32c02a5cda..d5776a4098 100644 --- a/heat_integrationtests/functional/test_unicode_template.py +++ b/heat_integrationtests/functional/test_unicode_template.py @@ -72,6 +72,12 @@ outputs: 'Label': u'\u6807\u7b7e', 'NoEcho': 'false', 'Type': 'Number'} + }, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {}, + 'resource_registry': {u'resources': {}} } } self.assertEqual(expected, ret) @@ -90,6 +96,12 @@ outputs: 'Label': u'\u6807\u7b7e', 'NoEcho': 'false', 'Type': 'Number'} + }, + 'Environment': { + 'event_sinks': [], + 'parameter_defaults': {}, + 'parameters': {u'\u53c2\u6570': 5}, + 'resource_registry': {u'resources': {}} } } self.assertEqual(expected, ret) diff --git a/releasenotes/notes/environment_validate_template-fee21a03bb628446.yaml b/releasenotes/notes/environment_validate_template-fee21a03bb628446.yaml new file mode 100644 index 0000000000..e5b07970fc --- /dev/null +++ b/releasenotes/notes/environment_validate_template-fee21a03bb628446.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The template validate API call now returns the Environment calculated by + heat - this enables preview of the merged environment when using + parameter_merge_strategy prior to creating the stack