From b8708fa67347980914c97f92a8c48dfa7a41c6ab Mon Sep 17 00:00:00 2001 From: Vahid Hashemian Date: Tue, 1 Dec 2015 13:45:22 -0800 Subject: [PATCH] Fix issues with CSAR error handling Add proper checks so the parser execution is not interrupted by errors found in the CSAR. Change-Id: I7f8f21b48a5ae0d60fcab83b401670dced2e3db3 Closes-Bug: #1521751 --- toscaparser/prereq/csar.py | 69 +++++++++++++++++++++----------- toscaparser/tests/test_prereq.py | 6 +-- toscaparser/tosca_template.py | 13 +++--- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/toscaparser/prereq/csar.py b/toscaparser/prereq/csar.py index 0f44c94..1cba5c4 100644 --- a/toscaparser/prereq/csar.py +++ b/toscaparser/prereq/csar.py @@ -37,6 +37,7 @@ class CSAR(object): self.path = csar_file self.a_file = a_file self.is_validated = False + self.error_caught = False self.csar = None self.temp_dir = None @@ -51,12 +52,14 @@ class CSAR(object): if not os.path.isfile(self.path): ExceptionCollector.appendException( ValidationError(message=missing_err_msg)) + return False else: self.csar = self.path else: # a URL if not UrlUtils.validate_url(self.path): ExceptionCollector.appendException( ValidationError(message=missing_err_msg)) + return False else: response = requests.get(self.path) self.csar = BytesIO(response.content) @@ -66,6 +69,7 @@ class CSAR(object): err_msg = (_('"%s" is not a valid zip file.') % self.path) ExceptionCollector.appendException( ValidationError(message=err_msg)) + return False # validate that it contains the metadata file in the correct location self.zfile = zipfile.ZipFile(self.csar, 'r') @@ -76,6 +80,7 @@ class CSAR(object): '"TOSCA-Metadata".') % self.path) ExceptionCollector.appendException( ValidationError(message=err_msg)) + return False # validate that 'Entry-Definitions' property exists in TOSCA.meta data = self.zfile.read('TOSCA-Metadata/TOSCA.meta') @@ -84,33 +89,40 @@ class CSAR(object): 'content.') % self.path) try: meta = yaml.load(data) - if type(meta) is not dict: + if type(meta) is dict: + self.metadata = meta + else: ExceptionCollector.appendException( ValidationError(message=invalid_yaml_err_msg)) - self.metadata = meta + return False except yaml.YAMLError: ExceptionCollector.appendException( ValidationError(message=invalid_yaml_err_msg)) + return False if 'Entry-Definitions' not in self.metadata: err_msg = (_('The CSAR "%s" is missing the required metadata ' - '"Entry-Definitions" in "TOSCA-Metadata/TOSCA.meta".') + '"Entry-Definitions" in ' + '"TOSCA-Metadata/TOSCA.meta".') % self.path) ExceptionCollector.appendException( ValidationError(message=err_msg)) + return False # validate that 'Entry-Definitions' metadata value points to an # existing file in the CSAR - entry = self.metadata['Entry-Definitions'] - if entry not in filelist: - err_msg = (_('The "Entry-Definitions" file defined in the CSAR ' - '"%s" does not exist.') % self.path) + entry = self.metadata.get('Entry-Definitions') + if entry and entry not in filelist: + err_msg = (_('The "Entry-Definitions" file defined in the ' + 'CSAR "%s" does not exist.') % self.path) ExceptionCollector.appendException( ValidationError(message=err_msg)) + return False - # validate that external references in the main template actually exist - # and are accessible + # validate that external references in the main template actually + # exist and are accessible self._validate_external_references() + return not self.error_caught def get_metadata(self): """Return the metadata dictionary.""" @@ -125,7 +137,7 @@ class CSAR(object): def _get_metadata(self, key): if not self.is_validated: self.validate() - return self.metadata[key] if key in self.metadata else None + return self.metadata.get(key) def get_author(self): return self._get_metadata('Created-By') @@ -134,24 +146,27 @@ class CSAR(object): return self._get_metadata('CSAR-Version') def get_main_template(self): - return self._get_metadata('Entry-Definitions') + entry_def = self._get_metadata('Entry-Definitions') + if entry_def in self.zfile.namelist(): + return entry_def def get_main_template_yaml(self): main_template = self.get_main_template() - data = self.zfile.read(main_template) - invalid_tosca_yaml_err_msg = ( - _('The file "%(template)s" in the CSAR "%(csar)s" does not ' - 'contain valid TOSCA YAML content.') % - {'template': main_template, 'csar': self.path}) - try: - tosca_yaml = yaml.load(data) - if type(tosca_yaml) is not dict: + if main_template: + data = self.zfile.read(main_template) + invalid_tosca_yaml_err_msg = ( + _('The file "%(template)s" in the CSAR "%(csar)s" does not ' + 'contain valid TOSCA YAML content.') % + {'template': main_template, 'csar': self.path}) + try: + tosca_yaml = yaml.load(data) + if type(tosca_yaml) is not dict: + ExceptionCollector.appendException( + ValidationError(message=invalid_tosca_yaml_err_msg)) + return tosca_yaml + except Exception: ExceptionCollector.appendException( ValidationError(message=invalid_tosca_yaml_err_msg)) - return tosca_yaml - except Exception: - ExceptionCollector.appendException( - ValidationError(message=invalid_tosca_yaml_err_msg)) def get_description(self): desc = self._get_metadata('Description') @@ -159,7 +174,7 @@ class CSAR(object): return desc self.metadata['Description'] = \ - self.get_main_template_yaml()['description'] + self.get_main_template_yaml().get('description') return self.metadata['Description'] def decompress(self): @@ -180,6 +195,8 @@ class CSAR(object): try: self.decompress() main_tpl_file = self.get_main_template() + if not main_tpl_file: + return main_tpl = self.get_main_template_yaml() if 'imports' in main_tpl: @@ -212,6 +229,7 @@ class CSAR(object): ValueError(_('Unexpected artifact ' 'definition for "%s".') % artifact_key)) + self.error_caught = True if 'interfaces' in node_template: interfaces = node_template['interfaces'] for interface_key in interfaces: @@ -250,9 +268,11 @@ class CSAR(object): else: ExceptionCollector.appendException( URLException(what=msg)) + self.error_caught = True except Exception: ExceptionCollector.appendException( URLException(what=msg)) + self.error_caught = True if os.path.isfile(os.path.join(self.temp_dir, os.path.dirname(tpl_file), @@ -263,3 +283,4 @@ class CSAR(object): ExceptionCollector.appendException( ValueError(_('The resource "%s" does not exist.') % resource_file)) + self.error_caught = True diff --git a/toscaparser/tests/test_prereq.py b/toscaparser/tests/test_prereq.py index f3277a3..98d616f 100644 --- a/toscaparser/tests/test_prereq.py +++ b/toscaparser/tests/test_prereq.py @@ -157,7 +157,7 @@ class CSARPrereqTest(TestCase): def test_valid_csar(self): path = os.path.join(self.base_path, "data/CSAR/csar_hello_world.zip") csar = CSAR(path) - self.assertIsNone(csar.validate()) + self.assertTrue(csar.validate()) self.assertTrue(csar.temp_dir is None or not os.path.exists(csar.temp_dir)) @@ -165,7 +165,7 @@ class CSARPrereqTest(TestCase): path = os.path.join(self.base_path, "data/CSAR/csar_wordpress_with_url" "_import_and_script.zip") csar = CSAR(path) - self.assertIsNone(csar.validate()) + self.assertTrue(csar.validate()) self.assertTrue(csar.temp_dir is None or not os.path.exists(csar.temp_dir)) @@ -225,6 +225,6 @@ class CSARPrereqTest(TestCase): def test_alternate_csar_extension(self): path = os.path.join(self.base_path, "data/CSAR/csar_elk.csar") csar = CSAR(path) - self.assertIsNone(csar.validate()) + self.assertTrue(csar.validate()) self.assertTrue(csar.temp_dir is None or not os.path.exists(csar.temp_dir)) diff --git a/toscaparser/tosca_template.py b/toscaparser/tosca_template.py index 3da2df4..f58ea82 100644 --- a/toscaparser/tosca_template.py +++ b/toscaparser/tosca_template.py @@ -54,6 +54,7 @@ class ToscaTemplate(object): def __init__(self, path, parsed_params=None, a_file=True): ExceptionCollector.start() self.a_file = a_file + self.input_path = path self.path = self._get_path(path) if self.path: self.tpl = YAML_LOADER(self.path, self.a_file) @@ -187,10 +188,10 @@ class ToscaTemplate(object): elif path.lower().endswith(('.zip', '.csar')): # a CSAR archive csar = CSAR(path, self.a_file) - csar.validate() - csar.decompress() - self.a_file = True # the file has been decompressed locally - return os.path.join(csar.temp_dir, csar.get_main_template()) + if csar.validate(): + csar.decompress() + self.a_file = True # the file has been decompressed locally + return os.path.join(csar.temp_dir, csar.get_main_template()) else: ExceptionCollector.appendException( ValueError(_('"%(path)s" is not a valid file.') @@ -201,10 +202,10 @@ class ToscaTemplate(object): raise ValidationError( message=(_('\nThe input "%(path)s" failed validation with the ' 'following error(s): \n\n\t') - % {'path': self.path}) + + % {'path': self.input_path}) + '\n\t'.join(ExceptionCollector.getExceptionsReport())) else: msg = (_('The input "%(path)s" successfully passed validation.') % - {'path': self.path}) + {'path': self.input_path}) log.info(msg) print(msg)