Add resource_definitions check to Stack.validate
We should check resource attributes' types in validate method for preventing stack creation with incorrect resources. So this patch splits resource_definitions() methods on validate_resource_definitions() and resource_definitions() methods and add validate_resource_definitions() to Stack.validate method. Change-Id: I16b35fc105154d9391f64285e90c396537fe3bb7 Partial-bug: #1397969
This commit is contained in:
parent
69a3599767
commit
3f16eebd06
|
@ -15,6 +15,7 @@ import collections
|
|||
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine import function
|
||||
|
@ -87,72 +88,77 @@ class CfnTemplate(template.Template):
|
|||
user_params=user_params,
|
||||
param_defaults=param_defaults)
|
||||
|
||||
def validate_resource_definitions(self, stack):
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
allowed_keys = set(_RESOURCE_KEYS)
|
||||
|
||||
try:
|
||||
|
||||
for name, snippet in resources.items():
|
||||
data = self.parse(stack, snippet)
|
||||
|
||||
if not self.validate_resource_key_type(RES_TYPE,
|
||||
six.string_types,
|
||||
'string',
|
||||
allowed_keys,
|
||||
name, data):
|
||||
args = {'name': name, 'type_key': RES_TYPE}
|
||||
msg = _('Resource %(name)s is missing '
|
||||
'"%(type_key)s"') % args
|
||||
raise KeyError(msg)
|
||||
|
||||
self.validate_resource_key_type(
|
||||
RES_PROPERTIES,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_METADATA,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_DEPENDS_ON,
|
||||
collections.Sequence,
|
||||
'list or string', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_DELETION_POLICY,
|
||||
six.string_types,
|
||||
'string', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_UPDATE_POLICY,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_DESCRIPTION,
|
||||
six.string_types,
|
||||
'string', allowed_keys, name, data)
|
||||
except TypeError as ex:
|
||||
raise exception.StackValidationFailed(message=six.text_type(ex))
|
||||
|
||||
def resource_definitions(self, stack):
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
|
||||
def rsrc_defn_item(name, snippet):
|
||||
data = self.parse(stack, snippet)
|
||||
|
||||
def get_check_type(key, valid_types, typename, default=None):
|
||||
if key in data:
|
||||
field = data[key]
|
||||
if not isinstance(field, valid_types):
|
||||
args = {'name': name, 'key': key, 'typename': typename}
|
||||
msg = _('Resource %(name)s %(key)s type '
|
||||
'must be %(typename)s') % args
|
||||
raise TypeError(msg)
|
||||
return field
|
||||
else:
|
||||
return default
|
||||
|
||||
resource_type = get_check_type(RES_TYPE,
|
||||
six.string_types,
|
||||
'string')
|
||||
if resource_type is None:
|
||||
args = {'name': name, 'type_key': RES_TYPE}
|
||||
msg = _('Resource %(name)s is missing "%(type_key)s"') % args
|
||||
raise KeyError(msg)
|
||||
|
||||
properties = get_check_type(RES_PROPERTIES,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
metadata = get_check_type(RES_METADATA,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
depends = get_check_type(RES_DEPENDS_ON,
|
||||
collections.Sequence,
|
||||
'list or string',
|
||||
default=[])
|
||||
if isinstance(depends, six.string_types):
|
||||
depends = data.get(RES_DEPENDS_ON)
|
||||
if not depends:
|
||||
depends = []
|
||||
elif isinstance(depends, six.string_types):
|
||||
depends = [depends]
|
||||
|
||||
deletion_policy = get_check_type(RES_DELETION_POLICY,
|
||||
six.string_types,
|
||||
'string')
|
||||
kwargs = {
|
||||
'resource_type': data.get(RES_TYPE),
|
||||
'properties': data.get(RES_PROPERTIES),
|
||||
'metadata': data.get(RES_METADATA),
|
||||
'depends': depends,
|
||||
'deletion_policy': data.get(RES_DELETION_POLICY),
|
||||
'update_policy': data.get(RES_UPDATE_POLICY),
|
||||
'description': data.get(RES_DESCRIPTION) or ''
|
||||
}
|
||||
|
||||
update_policy = get_check_type(RES_UPDATE_POLICY,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
description = get_check_type(RES_DESCRIPTION,
|
||||
six.string_types,
|
||||
'string',
|
||||
default='')
|
||||
|
||||
defn = rsrc_defn.ResourceDefinition(
|
||||
name, resource_type,
|
||||
properties=properties,
|
||||
metadata=metadata,
|
||||
depends=depends,
|
||||
deletion_policy=deletion_policy,
|
||||
update_policy=update_policy,
|
||||
description=description)
|
||||
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
|
||||
return name, defn
|
||||
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
return dict(rsrc_defn_item(name, data)
|
||||
for name, data in resources.items())
|
||||
|
||||
|
|
|
@ -191,74 +191,72 @@ class HOTemplate20130523(template.Template):
|
|||
user_params=user_params,
|
||||
param_defaults=param_defaults)
|
||||
|
||||
def resource_definitions(self, stack):
|
||||
def validate_resource_definitions(self, stack):
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
allowed_keys = set(_RESOURCE_KEYS)
|
||||
|
||||
try:
|
||||
for name, snippet in resources.items():
|
||||
data = self.parse(stack, snippet)
|
||||
|
||||
if not self.validate_resource_key_type(RES_TYPE,
|
||||
six.string_types,
|
||||
'string',
|
||||
allowed_keys,
|
||||
name, data):
|
||||
args = {'name': name, 'type_key': RES_TYPE}
|
||||
msg = _('Resource %(name)s is missing '
|
||||
'"%(type_key)s"') % args
|
||||
raise KeyError(msg)
|
||||
|
||||
self.validate_resource_key_type(
|
||||
RES_PROPERTIES,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_METADATA,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_DEPENDS_ON,
|
||||
collections.Sequence,
|
||||
'list or string', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_DELETION_POLICY,
|
||||
six.string_types,
|
||||
'string', allowed_keys, name, data)
|
||||
self.validate_resource_key_type(
|
||||
RES_UPDATE_POLICY,
|
||||
(collections.Mapping, function.Function),
|
||||
'object', allowed_keys, name, data)
|
||||
except (TypeError, ValueError) as ex:
|
||||
raise exception.StackValidationFailed(message=six.text_type(ex))
|
||||
|
||||
def resource_definitions(self, stack):
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
|
||||
def rsrc_defn_item(name, snippet):
|
||||
data = self.parse(stack, snippet)
|
||||
|
||||
def get_check_type(key, valid_types, typename, default=None):
|
||||
if key in data:
|
||||
field = data[key]
|
||||
if not isinstance(field, valid_types):
|
||||
args = {'name': name, 'key': key, 'typename': typename}
|
||||
msg = _('Resource %(name)s %(key)s type '
|
||||
'must be %(typename)s') % args
|
||||
raise TypeError(msg)
|
||||
return field
|
||||
else:
|
||||
return default
|
||||
|
||||
resource_type = get_check_type(RES_TYPE,
|
||||
six.string_types,
|
||||
'string')
|
||||
if resource_type is None:
|
||||
args = {'name': name, 'type_key': RES_TYPE}
|
||||
msg = _('Resource %(name)s is missing "%(type_key)s"') % args
|
||||
raise KeyError(msg)
|
||||
|
||||
properties = get_check_type(RES_PROPERTIES,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
metadata = get_check_type(RES_METADATA,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
depends = get_check_type(RES_DEPENDS_ON,
|
||||
collections.Sequence,
|
||||
'list or string',
|
||||
default=[])
|
||||
if isinstance(depends, six.string_types):
|
||||
depends = data.get(RES_DEPENDS_ON)
|
||||
if not depends:
|
||||
depends = []
|
||||
elif isinstance(depends, six.string_types):
|
||||
depends = [depends]
|
||||
|
||||
deletion_policy = get_check_type(RES_DELETION_POLICY,
|
||||
six.string_types,
|
||||
'string')
|
||||
kwargs = {
|
||||
'resource_type': data.get(RES_TYPE),
|
||||
'properties': data.get(RES_PROPERTIES),
|
||||
'metadata': data.get(RES_METADATA),
|
||||
'depends': depends,
|
||||
'deletion_policy': data.get(RES_DELETION_POLICY),
|
||||
'update_policy': data.get(RES_UPDATE_POLICY),
|
||||
'description': None
|
||||
}
|
||||
|
||||
update_policy = get_check_type(RES_UPDATE_POLICY,
|
||||
(collections.Mapping,
|
||||
function.Function),
|
||||
'object')
|
||||
|
||||
for key in data:
|
||||
if key not in allowed_keys:
|
||||
raise ValueError(_('"%s" is not a valid keyword '
|
||||
'inside a resource definition') % key)
|
||||
|
||||
defn = rsrc_defn.ResourceDefinition(
|
||||
name, resource_type,
|
||||
properties=properties,
|
||||
metadata=metadata,
|
||||
depends=depends,
|
||||
deletion_policy=deletion_policy,
|
||||
update_policy=update_policy,
|
||||
description=None)
|
||||
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
|
||||
return name, defn
|
||||
|
||||
resources = self.t.get(self.RESOURCES) or {}
|
||||
return dict(rsrc_defn_item(name, data)
|
||||
for name, data in resources.items())
|
||||
|
||||
|
|
|
@ -469,6 +469,9 @@ class Stack(collections.Mapping):
|
|||
parameter_groups = param_groups.ParameterGroups(self.t)
|
||||
parameter_groups.validate()
|
||||
|
||||
# Validate types of sections in ResourceDefinitions
|
||||
self.t.validate_resource_definitions(self)
|
||||
|
||||
# Check duplicate names between parameters and resources
|
||||
dup_names = set(self.parameters.keys()) & set(self.keys())
|
||||
|
||||
|
|
|
@ -158,6 +158,34 @@ class Template(collections.Mapping):
|
|||
'''Return a parameters.Parameters object for the stack.'''
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def validate_resource_key_type(cls, key, valid_types, typename,
|
||||
allowed_keys, rsrc_name, rsrc_data):
|
||||
"""Validation type of the specific resource key.
|
||||
|
||||
Used in validate_resource_definition and check correctness of
|
||||
key's type.
|
||||
"""
|
||||
if key not in allowed_keys:
|
||||
raise ValueError(_('"%s" is not a valid '
|
||||
'keyword inside a resource '
|
||||
'definition') % key)
|
||||
if key in rsrc_data:
|
||||
if not isinstance(rsrc_data.get(key), valid_types):
|
||||
args = {'name': rsrc_name, 'key': key,
|
||||
'typename': typename}
|
||||
message = _('Resource %(name)s %(key)s type '
|
||||
'must be %(typename)s') % args
|
||||
raise TypeError(message)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate_resource_definitions(self, stack):
|
||||
"""Check section's type of ResourceDefinitions."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def resource_definitions(self, stack):
|
||||
'''Return a dictionary of ResourceDefinition objects.'''
|
||||
|
|
|
@ -59,6 +59,9 @@ class TestTemplatePluginManager(common.HeatTestCase):
|
|||
def parameters(self, stack_identifier, user_params):
|
||||
pass
|
||||
|
||||
def validate_resource_definitions(self, stack):
|
||||
pass
|
||||
|
||||
def resource_definitions(self, stack):
|
||||
pass
|
||||
|
||||
|
|
|
@ -1468,3 +1468,28 @@ class validateTest(common.HeatTestCase):
|
|||
error_message = ('Arguments to "get_attr" must be of the form '
|
||||
'[resource_name, attribute, (path), ...]')
|
||||
self.assertEqual(error_message, six.text_type(err))
|
||||
|
||||
def test_validate_resource_attr_invalid_type(self):
|
||||
t = template_format.parse("""
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
resource:
|
||||
type: 123
|
||||
""")
|
||||
template = parser.Template(t)
|
||||
stack = parser.Stack(self.ctx, 'test_stack', template)
|
||||
ex = self.assertRaises(exception.StackValidationFailed, stack.validate)
|
||||
self.assertEqual('Resource resource type type must be string',
|
||||
six.text_type(ex))
|
||||
|
||||
def test_validate_resource_attr_invalid_type_cfn(self):
|
||||
t = template_format.parse("""
|
||||
HeatTemplateFormatVersion: '2012-12-12'
|
||||
Resources:
|
||||
Resource:
|
||||
Type: [Wrong, Type]
|
||||
""")
|
||||
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(t))
|
||||
ex = self.assertRaises(exception.StackValidationFailed, stack.validate)
|
||||
self.assertEqual('Resource Resource Type type must be string',
|
||||
six.text_type(ex))
|
||||
|
|
Loading…
Reference in New Issue