Allow nested validation for template-validate
Adds an optional flag that causes template-validate to recursively validate nested stacks and expose a schema of the parameters for all stacks in the tree. This uses a similar method to stack preview in that it builds the tree in memory so we can then walk it and extract the parameter data we need. Change-Id: I7e3d752124997a0a7b8ff53e774ed8034dd2f81d Co-Authored-By: Jay Dobies <jdobies@redhat.com> Implements-blueprint: nested-validation
This commit is contained in:
@@ -535,10 +535,20 @@ class StackController(object):
|
|||||||
|
|
||||||
data = InstantiationData(body)
|
data = InstantiationData(body)
|
||||||
|
|
||||||
|
whitelist = {'show_nested': 'single'}
|
||||||
|
params = util.get_allowed_params(req.params, whitelist)
|
||||||
|
|
||||||
|
show_nested = False
|
||||||
|
p_name = rpc_api.PARAM_SHOW_NESTED
|
||||||
|
if p_name in params:
|
||||||
|
params[p_name] = self._extract_bool_param(p_name, params[p_name])
|
||||||
|
show_nested = params[p_name]
|
||||||
|
|
||||||
result = self.rpc_client.validate_template(req.context,
|
result = self.rpc_client.validate_template(req.context,
|
||||||
data.template(),
|
data.template(),
|
||||||
data.environment(),
|
data.environment(),
|
||||||
files=data.files())
|
files=data.files(),
|
||||||
|
show_nested=show_nested)
|
||||||
|
|
||||||
if 'Error' in result:
|
if 'Error' in result:
|
||||||
raise exc.HTTPBadRequest(result['Error'])
|
raise exc.HTTPBadRequest(result['Error'])
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ class EngineService(service.Service):
|
|||||||
by the RPC caller.
|
by the RPC caller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.17'
|
RPC_API_VERSION = '1.18'
|
||||||
|
|
||||||
def __init__(self, host, topic):
|
def __init__(self, host, topic):
|
||||||
super(EngineService, self).__init__()
|
super(EngineService, self).__init__()
|
||||||
@@ -953,13 +953,17 @@ class EngineService(service.Service):
|
|||||||
engine_id=engine_id)
|
engine_id=engine_id)
|
||||||
|
|
||||||
@context.request_context
|
@context.request_context
|
||||||
def validate_template(self, cnxt, template, params=None, files=None):
|
def validate_template(self, cnxt, template, params=None, files=None,
|
||||||
"""Uses the stack parser to check the validity of a template.
|
show_nested=False):
|
||||||
|
"""
|
||||||
|
The validate_template method uses the stack parser to check
|
||||||
|
the validity of a template.
|
||||||
|
|
||||||
:param cnxt: RPC context.
|
:param cnxt: RPC context.
|
||||||
:param template: Template of stack you want to create.
|
:param template: Template of stack you want to create.
|
||||||
:param params: Stack Input Params
|
:param params: Stack Input Params
|
||||||
:param files: Files referenced from the template
|
:param files: Files referenced from the template
|
||||||
|
:param show_nested: if True, any nested templates will be checked
|
||||||
"""
|
"""
|
||||||
LOG.info(_LI('validate_template'))
|
LOG.info(_LI('validate_template'))
|
||||||
if template is None:
|
if template is None:
|
||||||
@@ -996,6 +1000,30 @@ class EngineService(service.Service):
|
|||||||
if param_groups.parameter_groups:
|
if param_groups.parameter_groups:
|
||||||
result['ParameterGroups'] = param_groups.parameter_groups
|
result['ParameterGroups'] = param_groups.parameter_groups
|
||||||
|
|
||||||
|
if show_nested:
|
||||||
|
# Note preview_resources is needed here to build the tree
|
||||||
|
# of nested resources/stacks in memory, otherwise the
|
||||||
|
# nested/has_nested() tests below won't work
|
||||||
|
stack.preview_resources()
|
||||||
|
|
||||||
|
def nested_params(stk):
|
||||||
|
n_result = {}
|
||||||
|
for r in stk:
|
||||||
|
if stk[r].has_nested():
|
||||||
|
n_params = stk[r].nested().parameters.map(
|
||||||
|
api.format_validate_parameter,
|
||||||
|
filter_func=filter_parameter)
|
||||||
|
n_result[r] = {
|
||||||
|
'Type': stk[r].type(),
|
||||||
|
'Description': stk[r].nested().t.get(
|
||||||
|
'Description', ''),
|
||||||
|
'Parameters': n_params
|
||||||
|
}
|
||||||
|
n_result[r].update(nested_params(stk[r].nested()))
|
||||||
|
return {'NestedParameters': n_result} if n_result else {}
|
||||||
|
|
||||||
|
result.update(nested_params(stack))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@context.request_context
|
@context.request_context
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class EngineClient(object):
|
|||||||
1.15 - Add preview_update_stack() call
|
1.15 - Add preview_update_stack() call
|
||||||
1.16 - Adds version, type_name to list_resource_types()
|
1.16 - Adds version, type_name to list_resource_types()
|
||||||
1.17 - Add files to validate_template
|
1.17 - Add files to validate_template
|
||||||
|
1.18 - Add show_nested to validate_template
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.0'
|
BASE_RPC_API_VERSION = '1.0'
|
||||||
@@ -293,7 +294,8 @@ class EngineClient(object):
|
|||||||
),
|
),
|
||||||
version='1.15')
|
version='1.15')
|
||||||
|
|
||||||
def validate_template(self, ctxt, template, params=None, files=None):
|
def validate_template(self, ctxt, template, params=None, files=None,
|
||||||
|
show_nested=False):
|
||||||
"""
|
"""
|
||||||
The validate_template method uses the stack parser to check
|
The validate_template method uses the stack parser to check
|
||||||
the validity of a template.
|
the validity of a template.
|
||||||
@@ -302,12 +304,14 @@ class EngineClient(object):
|
|||||||
:param template: Template of stack you want to create.
|
:param template: Template of stack you want to create.
|
||||||
:param params: Stack Input Params/Environment
|
:param params: Stack Input Params/Environment
|
||||||
:param files: files referenced from the environment/template.
|
:param files: files referenced from the environment/template.
|
||||||
|
:param show_nested: if True nested templates will be validated
|
||||||
"""
|
"""
|
||||||
return self.call(ctxt, self.make_msg('validate_template',
|
return self.call(ctxt, self.make_msg('validate_template',
|
||||||
template=template,
|
template=template,
|
||||||
params=params,
|
params=params,
|
||||||
files=files),
|
files=files,
|
||||||
version='1.17')
|
show_nested=show_nested),
|
||||||
|
version='1.18')
|
||||||
|
|
||||||
def authenticated_to_backend(self, ctxt):
|
def authenticated_to_backend(self, ctxt):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1074,8 +1074,8 @@ class CfnStackControllerTest(common.HeatTestCase):
|
|||||||
rpc_client.EngineClient.call(
|
rpc_client.EngineClient.call(
|
||||||
dummy_req.context,
|
dummy_req.context,
|
||||||
('validate_template', {'template': json_template, 'params': None,
|
('validate_template', {'template': json_template, 'params': None,
|
||||||
'files': None}),
|
'files': None, 'show_nested': False}),
|
||||||
version='1.17'
|
version='1.18'
|
||||||
).AndReturn(response)
|
).AndReturn(response)
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
|||||||
@@ -2059,8 +2059,9 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
|
|||||||
'encrypted_param_names': [],
|
'encrypted_param_names': [],
|
||||||
'parameter_defaults': {},
|
'parameter_defaults': {},
|
||||||
'resource_registry': {}},
|
'resource_registry': {}},
|
||||||
'files': {}}),
|
'files': {},
|
||||||
version='1.17'
|
'show_nested': False}),
|
||||||
|
version='1.18'
|
||||||
).AndReturn(engine_response)
|
).AndReturn(engine_response)
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@@ -2086,8 +2087,9 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
|
|||||||
'encrypted_param_names': [],
|
'encrypted_param_names': [],
|
||||||
'parameter_defaults': {},
|
'parameter_defaults': {},
|
||||||
'resource_registry': {}},
|
'resource_registry': {}},
|
||||||
'files': {}}),
|
'files': {},
|
||||||
version='1.17'
|
'show_nested': False}),
|
||||||
|
version='1.18'
|
||||||
).AndReturn({'Error': 'fubar'})
|
).AndReturn({'Error': 'fubar'})
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ServiceEngineTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def test_make_sure_rpc_version(self):
|
def test_make_sure_rpc_version(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'1.17',
|
'1.18',
|
||||||
service.EngineService.RPC_API_VERSION,
|
service.EngineService.RPC_API_VERSION,
|
||||||
('RPC version is changed, please update this test to new version '
|
('RPC version is changed, please update this test to new version '
|
||||||
'and make sure additional test cases are added for RPC APIs '
|
'and make sure additional test cases are added for RPC APIs '
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ class EngineRpcAPITestCase(common.HeatTestCase):
|
|||||||
self._test_engine_api('validate_template', 'call',
|
self._test_engine_api('validate_template', 'call',
|
||||||
template={u'Foo': u'bar'},
|
template={u'Foo': u'bar'},
|
||||||
params={u'Egg': u'spam'},
|
params={u'Egg': u'spam'},
|
||||||
files=None)
|
files=None,
|
||||||
|
show_nested=False)
|
||||||
|
|
||||||
def test_list_resource_types(self):
|
def test_list_resource_types(self):
|
||||||
self._test_engine_api('list_resource_types',
|
self._test_engine_api('list_resource_types',
|
||||||
|
|||||||
@@ -35,6 +35,29 @@ resources:
|
|||||||
length: {get_param: aparam}
|
length: {get_param: aparam}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
parent_template = '''
|
||||||
|
heat_template_version: 2014-10-16
|
||||||
|
description: the parent template
|
||||||
|
parameters:
|
||||||
|
pparam:
|
||||||
|
type: number
|
||||||
|
default: 5
|
||||||
|
description: the param description
|
||||||
|
resources:
|
||||||
|
nres:
|
||||||
|
type: mynested.yaml
|
||||||
|
properties:
|
||||||
|
aparam: {get_param: pparam}
|
||||||
|
'''
|
||||||
|
|
||||||
|
parent_template_noprop = '''
|
||||||
|
heat_template_version: 2014-10-16
|
||||||
|
description: the parent template
|
||||||
|
resources:
|
||||||
|
nres:
|
||||||
|
type: mynested.yaml
|
||||||
|
'''
|
||||||
|
|
||||||
random_template_groups = '''
|
random_template_groups = '''
|
||||||
heat_template_version: 2014-10-16
|
heat_template_version: 2014-10-16
|
||||||
description: the stack description
|
description: the stack description
|
||||||
@@ -147,3 +170,75 @@ resources:
|
|||||||
'NoEcho': 'true',
|
'NoEcho': 'true',
|
||||||
'Type': 'String'}}}
|
'Type': 'String'}}}
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_template_validate_nested_off(self):
|
||||||
|
files = {'mynested.yaml': self.random_template}
|
||||||
|
ret = self.client.stacks.validate(template=self.parent_template,
|
||||||
|
files=files)
|
||||||
|
expected = {'Description': 'the parent template',
|
||||||
|
'Parameters': {
|
||||||
|
'pparam': {'Default': 5,
|
||||||
|
'Description': 'the param description',
|
||||||
|
'Label': 'pparam',
|
||||||
|
'NoEcho': 'false',
|
||||||
|
'Type': 'Number'}}}
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_template_validate_nested_on(self):
|
||||||
|
files = {'mynested.yaml': self.random_template}
|
||||||
|
ret = self.client.stacks.validate(template=self.parent_template_noprop,
|
||||||
|
files=files,
|
||||||
|
show_nested=True)
|
||||||
|
expected = {'Description': 'the parent template',
|
||||||
|
'Parameters': {},
|
||||||
|
'NestedParameters': {
|
||||||
|
'nres': {'Description': 'the stack description',
|
||||||
|
'Parameters': {'aparam': {'Default': 10,
|
||||||
|
'Description':
|
||||||
|
'the param '
|
||||||
|
'description',
|
||||||
|
'Label': 'aparam',
|
||||||
|
'NoEcho': 'false',
|
||||||
|
'Type': 'Number'}},
|
||||||
|
'Type': 'mynested.yaml'}}}
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_template_validate_nested_on_multiple(self):
|
||||||
|
# parent_template -> nested_template -> random_template
|
||||||
|
nested_template = self.random_template.replace(
|
||||||
|
'OS::Heat::RandomString', 'mynested2.yaml')
|
||||||
|
files = {'mynested.yaml': nested_template,
|
||||||
|
'mynested2.yaml': self.random_template}
|
||||||
|
ret = self.client.stacks.validate(template=self.parent_template,
|
||||||
|
files=files,
|
||||||
|
show_nested=True)
|
||||||
|
|
||||||
|
n_param2 = {'myres': {'Description': 'the stack description',
|
||||||
|
'Parameters': {'aparam': {'Default': 10,
|
||||||
|
'Description':
|
||||||
|
'the param '
|
||||||
|
'description',
|
||||||
|
'Label': 'aparam',
|
||||||
|
'NoEcho': 'false',
|
||||||
|
'Type': 'Number'}},
|
||||||
|
'Type': 'mynested2.yaml'}}
|
||||||
|
expected = {'Description': 'the parent template',
|
||||||
|
'Parameters': {
|
||||||
|
'pparam': {'Default': 5,
|
||||||
|
'Description': 'the param description',
|
||||||
|
'Label': 'pparam',
|
||||||
|
'NoEcho': 'false',
|
||||||
|
'Type': 'Number'}},
|
||||||
|
'NestedParameters': {
|
||||||
|
'nres': {'Description': 'the stack description',
|
||||||
|
'Parameters': {'aparam': {'Default': 10,
|
||||||
|
'Description':
|
||||||
|
'the param '
|
||||||
|
'description',
|
||||||
|
'Label': 'aparam',
|
||||||
|
'Value': 5,
|
||||||
|
'NoEcho': 'false',
|
||||||
|
'Type': 'Number'}},
|
||||||
|
'NestedParameters': n_param2,
|
||||||
|
'Type': 'mynested.yaml'}}}
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|||||||
Reference in New Issue
Block a user