Improve error reporting for missing nested template

When the template for a TemplateResource was missing from the files dict
and not otherwise available, we provided misleading feedback when
validating the parent template.

First, we log that we are fetching the template from a URL, even though we
would not actually attempt to do so if it is a file:/// URL. Move the log
to after the allowed scheme check, and log debug to indicate when we cannot
find a template file.

While an exception would be generated in
TemplateResource._generate_schema() at resource initialisation time, it is
suppressed and saved for later. An empty template is used to generate the
properties schema. The exception is re-raised when validate() is called,
but during a template validation we only call validate_template(), not
validate(), so the first error we run into will be a mismatch of property
names. (If no property values were passed in the parent template, we may
not even get an error even though we won't be able to create a stack from
the given data.) Also re-raise the stored exception at the beginning of
validate_template() so that users will see the true source of the error.

Change-Id: I1bc100684e1b84fc9ac54ef523d798b317e4dc51
Story: #1739447
Task: 22219
This commit is contained in:
Zane Bitter 2018-01-31 11:39:03 -05:00
parent a53219b522
commit 3a96fd7e25
3 changed files with 19 additions and 7 deletions

View File

@ -39,13 +39,13 @@ def get(url, allowed_schemes=('http', 'https')):
the allowed_schemes argument. the allowed_schemes argument.
Raise an IOError if getting the data fails. Raise an IOError if getting the data fails.
""" """
LOG.info('Fetching data from %s', url)
components = urllib.parse.urlparse(url) components = urllib.parse.urlparse(url)
if components.scheme not in allowed_schemes: if components.scheme not in allowed_schemes:
raise URLFetchError(_('Invalid URL scheme %s') % components.scheme) raise URLFetchError(_('Invalid URL scheme %s') % components.scheme)
LOG.info('Fetching data from %s', url)
if components.scheme == 'file': if components.scheme == 'file':
try: try:
return urllib.request.urlopen(url).read() return urllib.request.urlopen(url).read()

View File

@ -195,6 +195,10 @@ class TemplateResource(stack_resource.StackResource):
reported_excp = None reported_excp = None
t_data = self.stack.t.files.get(self.template_url) t_data = self.stack.t.files.get(self.template_url)
stored_t_data = t_data stored_t_data = t_data
if t_data is None:
LOG.debug('TemplateResource data file "%s" not found in files.',
self.template_url)
if not t_data and self.template_url.endswith((".yaml", ".template")): if not t_data and self.template_url.endswith((".yaml", ".template")):
try: try:
t_data = self.get_template_file(self.template_url, t_data = self.get_template_file(self.template_url,
@ -257,9 +261,8 @@ class TemplateResource(stack_resource.StackResource):
raise exception.StackValidationFailed(message=msg) raise exception.StackValidationFailed(message=msg)
def validate(self): def validate(self):
if self.validation_exception is not None: # Calls validate_template()
msg = six.text_type(self.validation_exception) result = super(TemplateResource, self).validate()
raise exception.StackValidationFailed(message=msg)
try: try:
self.template_data() self.template_data()
@ -280,7 +283,14 @@ class TemplateResource(stack_resource.StackResource):
facade_cls = fri.get_class(files=self.stack.t.files) facade_cls = fri.get_class(files=self.stack.t.files)
self._validate_against_facade(facade_cls) self._validate_against_facade(facade_cls)
return super(TemplateResource, self).validate() return result
def validate_template(self):
if self.validation_exception is not None:
msg = six.text_type(self.validation_exception)
raise exception.StackValidationFailed(message=msg)
return super(TemplateResource, self).validate_template()
def handle_adopt(self, resource_data=None): def handle_adopt(self, resource_data=None):
return self.create_with_template(self.child_template(), return self.create_with_template(self.child_template(),

View File

@ -792,9 +792,11 @@ outputs:
value: value:
not-important not-important
''' '''
template = yaml.safe_load(self.template)
del template['resources']['thisone']['properties']['two']
try: try:
self.stack_create( self.stack_create(
template=self.template, template=yaml.safe_dump(template),
environment=self.env, environment=self.env,
files={'facade.yaml': self.templ_facade, files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_parameter}, 'concrete.yaml': templ_missing_parameter},