heat/heat/engine/template_common.py
Thomas Herve 820b5a8d84 Handle invalid depends field
Raise a proper error when something wrong is passed in a depends_on
field.

Change-Id: I9d27cc48a7f4c050fcc89ab5d07b19329d228955
Story: 2002544
Task: 22103
2018-06-25 15:07:33 +02:00

214 lines
9.1 KiB
Python

#
# 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.
import collections
import functools
import weakref
import six
from heat.common import exception
from heat.common.i18n import _
from heat.engine import conditions
from heat.engine import function
from heat.engine import output
from heat.engine import template
class CommonTemplate(template.Template):
"""A class of the common implementation for HOT and CFN templates.
This is *not* a stable interface, and any third-parties who create derived
classes from it do so at their own risk.
"""
def __init__(self, template, template_id=None, files=None, env=None):
super(CommonTemplate, self).__init__(template, template_id=template_id,
files=files, env=env)
self._conditions_cache = None, None
@classmethod
def _parse_resource_field(cls, key, valid_types, typename,
rsrc_name, rsrc_data, parse_func):
"""Parse a field in a resource definition.
:param key: The name of the key
:param valid_types: Valid types for the parsed output
:param typename: Description of valid type to include in error output
:param rsrc_name: The resource name
:param rsrc_data: The unparsed resource definition data
:param parse_func: A function to parse the data, which takes the
contents of the field and its path in the template as arguments.
"""
if key in rsrc_data:
data = parse_func(rsrc_data[key], '.'.join([cls.RESOURCES,
rsrc_name,
key]))
if not isinstance(data, 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 data
else:
return None
def _rsrc_defn_args(self, stack, name, data):
if self.RES_TYPE not in data:
args = {'name': name, 'type_key': self.RES_TYPE}
msg = _('Resource %(name)s is missing "%(type_key)s"') % args
raise KeyError(msg)
parse = functools.partial(self.parse, stack)
def no_parse(field, path):
return field
yield ('resource_type',
self._parse_resource_field(self.RES_TYPE,
six.string_types, 'string',
name, data, parse))
yield ('properties',
self._parse_resource_field(self.RES_PROPERTIES,
(collections.Mapping,
function.Function), 'object',
name, data, parse))
yield ('metadata',
self._parse_resource_field(self.RES_METADATA,
(collections.Mapping,
function.Function), 'object',
name, data, parse))
depends = self._parse_resource_field(self.RES_DEPENDS_ON,
collections.Sequence,
'list or string',
name, data, no_parse)
if isinstance(depends, six.string_types):
depends = [depends]
elif depends:
for dep in depends:
if not isinstance(dep, six.string_types):
msg = _('Resource %(name)s %(key)s '
'must be a list of strings') % {
'name': name, 'key': self.RES_DEPENDS_ON}
raise exception.StackValidationFailed(message=msg)
yield 'depends', depends
del_policy = self._parse_resource_field(self.RES_DELETION_POLICY,
(six.string_types,
function.Function),
'string',
name, data, parse)
deletion_policy = function.resolve(del_policy)
if deletion_policy is not None:
if deletion_policy not in self.deletion_policies:
msg = _('Invalid deletion policy "%s"') % deletion_policy
raise exception.StackValidationFailed(message=msg)
else:
deletion_policy = self.deletion_policies[deletion_policy]
yield 'deletion_policy', deletion_policy
yield ('update_policy',
self._parse_resource_field(self.RES_UPDATE_POLICY,
(collections.Mapping,
function.Function), 'object',
name, data, parse))
yield ('description',
self._parse_resource_field(self.RES_DESCRIPTION,
six.string_types, 'string',
name, data, no_parse))
def _get_condition_definitions(self):
"""Return the condition definitions of template."""
return {}
def conditions(self, stack):
get_cache_stack, cached_conds = self._conditions_cache
if (cached_conds is not None and
get_cache_stack is not None and
get_cache_stack() is stack):
return cached_conds
raw_defs = self._get_condition_definitions()
if not isinstance(raw_defs, collections.Mapping):
message = _('Condition definitions must be a map. Found a '
'%s instead') % type(raw_defs).__name__
raise exception.StackValidationFailed(
error='Conditions validation error',
message=message)
parsed = {n: self.parse_condition(stack, c,
'.'.join([self.CONDITIONS, n]))
for n, c in raw_defs.items()}
conds = conditions.Conditions(parsed)
get_cache_stack = weakref.ref(stack) if stack is not None else None
self._conditions_cache = get_cache_stack, conds
return conds
def outputs(self, stack):
conds = self.conditions(stack)
outputs = self.t.get(self.OUTPUTS) or {}
def get_outputs():
for key, val in outputs.items():
if not isinstance(val, collections.Mapping):
message = _('Output definitions must be a map. Found a '
'%s instead') % type(val).__name__
raise exception.StackValidationFailed(
error='Output validation error',
path=[self.OUTPUTS, key],
message=message)
if self.OUTPUT_VALUE not in val:
message = _('Each output definition must contain '
'a %s key.') % self.OUTPUT_VALUE
raise exception.StackValidationFailed(
error='Output validation error',
path=[self.OUTPUTS, key],
message=message)
description = val.get(self.OUTPUT_DESCRIPTION)
if hasattr(self, 'OUTPUT_CONDITION'):
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
cond = self.parse_condition(stack,
val.get(self.OUTPUT_CONDITION),
'.'.join(path))
try:
enabled = conds.is_enabled(function.resolve(cond))
except ValueError as exc:
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
message = six.text_type(exc)
raise exception.StackValidationFailed(path=path,
message=message)
if not enabled:
yield key, output.OutputDefinition(key, None,
description)
continue
value_def = self.parse(stack, val[self.OUTPUT_VALUE],
path='.'.join([self.OUTPUTS, key,
self.OUTPUT_VALUE]))
yield key, output.OutputDefinition(key, value_def, description)
return dict(get_outputs())