2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.sdc.toscaparser.api.prereq;
23 import org.onap.sdc.toscaparser.api.ImportsLoader;
24 import org.onap.sdc.toscaparser.api.common.JToscaValidationIssue;
25 import org.onap.sdc.toscaparser.api.utils.ThreadLocalsHolder;
26 import org.onap.sdc.toscaparser.api.utils.UrlUtils;
28 import java.io.BufferedOutputStream;
30 import java.io.FileInputStream;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.RandomAccessFile;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.nio.file.StandardCopyOption;
41 import java.util.zip.ZipEntry;
42 import java.util.zip.ZipFile;
43 import java.util.zip.ZipInputStream;
45 import org.onap.sdc.toscaparser.api.common.JToscaException;
46 import org.onap.sdc.toscaparser.api.utils.JToscaErrorCodes;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.yaml.snakeyaml.Yaml;
53 private static Logger log = LoggerFactory.getLogger(CSAR.class.getName());
54 private static final ArrayList<String> META_PROPERTIES_FILES = new ArrayList<>(Arrays.asList("TOSCA-Metadata/TOSCA.meta", "csar.meta"));
57 private boolean isFile;
58 private boolean isValidated;
59 private boolean errorCaught;
61 private String tempDir;
62 // private Metadata metaData;
63 private File tempFile;
64 private LinkedHashMap<String, LinkedHashMap<String, Object>> metaProperties;
66 public CSAR(String csarPath, boolean aFile) {
74 metaProperties = new LinkedHashMap<>();
77 public boolean validate() throws JToscaException {
80 //validate that the file or URL exists
83 File f = new File(path);
85 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE220", String.format("\"%s\" is not a file", path)));
91 if (!UrlUtils.validateUrl(path)) {
92 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE221", String.format("ImportError: \"%s\" does not exist", path)));
95 // get it to a local file
97 File tempFile = File.createTempFile("csartmp", ".csar");
98 Path ptf = Paths.get(tempFile.getPath());
99 URL webfile = new URL(path);
100 InputStream in = webfile.openStream();
101 Files.copy(in, ptf, StandardCopyOption.REPLACE_EXISTING);
102 } catch (Exception e) {
103 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE222", "ImportError: failed to load CSAR from " + path));
107 log.debug("CSAR - validate - currently only files are supported");
111 _parseAndValidateMetaProperties();
117 // validate that external references in the main template actually exist and are accessible
118 _validateExternalReferences();
124 private void _parseAndValidateMetaProperties() throws JToscaException {
130 // validate that it is a valid zip file
131 RandomAccessFile raf = new RandomAccessFile(csar, "r");
132 long n = raf.readInt();
134 // check if Zip's magic number
135 if (n != 0x504B0304) {
136 String errorString = String.format("\"%s\" is not a valid zip file", csar);
137 log.error(errorString);
138 throw new JToscaException(errorString, JToscaErrorCodes.INVALID_CSAR_FORMAT.getValue());
141 // validate that it contains the metadata file in the correct location
142 zf = new ZipFile(csar);
143 ZipEntry ze = zf.getEntry("TOSCA-Metadata/TOSCA.meta");
146 String errorString = String.format(
147 "\"%s\" is not a valid CSAR as it does not contain the " +
148 "required file \"TOSCA.meta\" in the folder \"TOSCA-Metadata\"", csar);
149 log.error(errorString);
150 throw new JToscaException(errorString, JToscaErrorCodes.MISSING_META_FILE.getValue());
153 //Going over expected metadata files and parsing them
154 for (String metaFile : META_PROPERTIES_FILES) {
156 byte ba[] = new byte[4096];
157 ze = zf.getEntry(metaFile);
159 InputStream inputStream = zf.getInputStream(ze);
160 n = inputStream.read(ba, 0, 4096);
161 String md = new String(ba);
162 md = md.substring(0, (int) n);
164 String errorString = String.format(
165 "The file \"%s\" in the" +
166 " CSAR \"%s\" does not contain valid YAML content", ze.getName(), csar);
169 Yaml yaml = new Yaml();
170 Object mdo = yaml.load(md);
171 if (!(mdo instanceof LinkedHashMap)) {
172 log.error(errorString);
173 throw new JToscaException(errorString, JToscaErrorCodes.INVALID_META_YAML_CONTENT.getValue());
176 String[] split = ze.getName().split("/");
177 String fileName = split[split.length - 1];
179 if (!metaProperties.containsKey(fileName)) {
180 metaProperties.put(fileName, (LinkedHashMap<String, Object>) mdo);
182 } catch (Exception e) {
183 log.error(errorString);
184 throw new JToscaException(errorString, JToscaErrorCodes.INVALID_META_YAML_CONTENT.getValue());
189 // verify it has "Entry-Definition"
190 String edf = _getMetadata("Entry-Definitions");
192 String errorString = String.format(
193 "The CSAR \"%s\" is missing the required metadata " +
194 "\"Entry-Definitions\" in \"TOSCA-Metadata/TOSCA.meta\"", csar);
195 log.error(errorString);
196 throw new JToscaException(errorString, JToscaErrorCodes.ENTRY_DEFINITION_NOT_DEFINED.getValue());
199 //validate that "Entry-Definitions' metadata value points to an existing file in the CSAR
200 boolean foundEDF = false;
201 Enumeration<? extends ZipEntry> entries = zf.entries();
202 while (entries.hasMoreElements()) {
203 ze = entries.nextElement();
204 if (ze.getName().equals(edf)) {
210 String errorString = String.format(
211 "The \"Entry-Definitions\" file defined in the CSAR \"%s\" does not exist", csar);
212 log.error(errorString);
213 throw new JToscaException(errorString, JToscaErrorCodes.MISSING_ENTRY_DEFINITION_FILE.getValue());
215 } catch (JToscaException e) {
216 //ThreadLocalsHolder.getCollector().appendCriticalException(e.getMessage());
218 } catch (Exception e) {
219 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE223", "ValidationError: " + e.getMessage()));
227 } catch (IOException e) {
231 public void cleanup() {
233 if (tempFile != null) {
236 } catch (Exception e) {
240 private String _getMetadata(String key) throws JToscaException {
244 Object value = _getMetaProperty("TOSCA.meta").get(key);
245 return value != null ? value.toString() : null;
248 public String getAuthor() throws JToscaException {
249 return _getMetadata("Created-By");
252 public String getVersion() throws JToscaException {
253 return _getMetadata("CSAR-Version");
256 public LinkedHashMap<String, LinkedHashMap<String, Object>> getMetaProperties() {
257 return metaProperties;
260 private LinkedHashMap<String, Object> _getMetaProperty(String propertiesFile) {
261 return metaProperties.get(propertiesFile);
264 public String getMainTemplate() throws JToscaException {
265 String entryDef = _getMetadata("Entry-Definitions");
269 zf = new ZipFile(path);
270 ok = (zf.getEntry(entryDef) != null);
272 } catch (IOException e) {
274 log.error("CSAR - getMainTemplate - failed to open {}", path);
284 @SuppressWarnings("unchecked")
285 public LinkedHashMap<String, Object> getMainTemplateYaml() throws JToscaException {
286 String mainTemplate = tempDir + File.separator + getMainTemplate();
287 if (mainTemplate != null) {
288 try (InputStream input = new FileInputStream(new File(mainTemplate));) {
289 Yaml yaml = new Yaml();
290 Object data = yaml.load(input);
291 if (!(data instanceof LinkedHashMap)) {
292 throw new IOException();
294 return (LinkedHashMap<String, Object>) data;
295 } catch (Exception e) {
296 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE224", String.format(
297 "The file \"%s\" in the CSAR \"%s\" does not " +
298 "contain valid TOSCA YAML content",
299 mainTemplate, csar)));
305 public String getDescription() throws JToscaException {
306 String desc = _getMetadata("Description");
311 Map<String, Object> metaData = metaProperties.get("TOSCA.meta");
312 metaData.put("Description", getMainTemplateYaml().get("description"));
313 return _getMetadata("Description");
316 public String getTempDir() {
320 public void decompress() throws IOException, JToscaException {
325 if (tempDir == null || tempDir.isEmpty()) {
326 tempDir = Files.createTempDirectory("JTP").toString();
327 unzip(path, tempDir);
331 private void _validateExternalReferences() throws JToscaException {
332 // Extracts files referenced in the main template
333 // These references are currently supported:
335 // * interface implementations
339 String mainTplFile = getMainTemplate();
340 if (mainTplFile == null) {
344 LinkedHashMap<String, Object> mainTpl = getMainTemplateYaml();
345 if (mainTpl.get("imports") != null) {
346 // this loads the imports
347 ImportsLoader il = new ImportsLoader((ArrayList<Object>) mainTpl.get("imports"),
348 tempDir + File.separator + mainTplFile,
350 (LinkedHashMap<String, Object>) null);
353 if (mainTpl.get("topology_template") != null) {
354 LinkedHashMap<String, Object> topologyTemplate =
355 (LinkedHashMap<String, Object>) mainTpl.get("topology_template");
357 if (topologyTemplate.get("node_templates") != null) {
358 LinkedHashMap<String, Object> nodeTemplates =
359 (LinkedHashMap<String, Object>) topologyTemplate.get("node_templates");
360 for (String nodeTemplateKey : nodeTemplates.keySet()) {
361 LinkedHashMap<String, Object> nodeTemplate =
362 (LinkedHashMap<String, Object>) nodeTemplates.get(nodeTemplateKey);
363 if (nodeTemplate.get("artifacts") != null) {
364 LinkedHashMap<String, Object> artifacts =
365 (LinkedHashMap<String, Object>) nodeTemplate.get("artifacts");
366 for (String artifactKey : artifacts.keySet()) {
367 Object artifact = artifacts.get(artifactKey);
368 if (artifact instanceof String) {
369 _validateExternalReference(mainTplFile, (String) artifact, true);
370 } else if (artifact instanceof LinkedHashMap) {
371 String file = (String) ((LinkedHashMap<String, Object>) artifact).get("file");
373 _validateExternalReference(mainTplFile, file, true);
376 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE225", String.format(
377 "ValueError: Unexpected artifact definition for \"%s\"",
383 if (nodeTemplate.get("interfaces") != null) {
384 LinkedHashMap<String, Object> interfaces =
385 (LinkedHashMap<String, Object>) nodeTemplate.get("interfaces");
386 for (String interfaceKey : interfaces.keySet()) {
387 LinkedHashMap<String, Object> _interface =
388 (LinkedHashMap<String, Object>) interfaces.get(interfaceKey);
389 for (String operationKey : _interface.keySet()) {
390 Object operation = _interface.get(operationKey);
391 if (operation instanceof String) {
392 _validateExternalReference(mainTplFile, (String) operation, false);
393 } else if (operation instanceof LinkedHashMap) {
394 String imp = (String) ((LinkedHashMap<String, Object>) operation).get("implementation");
396 _validateExternalReference(mainTplFile, imp, true);
405 } catch (IOException e) {
408 // delete tempDir (only here?!?)
409 File fdir = new File(tempDir);
415 public static void deleteDir(File fdir) {
417 if (fdir.isDirectory()) {
418 for (File c : fdir.listFiles())
422 } catch (Exception e) {
426 private void _validateExternalReference(String tplFile, String resourceFile, boolean raiseExc) {
427 // Verify that the external resource exists
429 // If resource_file is a URL verify that the URL is valid.
430 // If resource_file is a relative path verify that the path is valid
431 // considering base folder (self.temp_dir) and tpl_file.
432 // Note that in a CSAR resource_file cannot be an absolute path.
433 if (UrlUtils.validateUrl(resourceFile)) {
434 String msg = String.format("URLException: The resource at \"%s\" cannot be accessed", resourceFile);
436 if (UrlUtils.isUrlAccessible(resourceFile)) {
439 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE226", msg));
442 } catch (Exception e) {
443 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE227", msg));
447 String dirPath = Paths.get(tplFile).getParent().toString();
448 String filePath = tempDir + File.separator + dirPath + File.separator + resourceFile;
449 File f = new File(filePath);
455 ThreadLocalsHolder.getCollector().appendValidationIssue(new JToscaValidationIssue("JE228", String.format(
456 "ValueError: The resource \"%s\" does not exist", resourceFile)));
461 private void unzip(String zipFilePath, String destDirectory) throws IOException {
462 File destDir = new File(destDirectory);
463 if (!destDir.exists()) {
467 try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));) {
468 ZipEntry entry = zipIn.getNextEntry();
469 // iterates over entries in the zip file
470 while (entry != null) {
471 // create all directories needed for nested items
472 String[] parts = entry.getName().split("/");
473 String s = destDirectory + File.separator;
474 for (int i = 0; i < parts.length - 1; i++) {
476 File idir = new File(s);
477 if (!idir.exists()) {
482 String filePath = destDirectory + File.separator + entry.getName();
483 if (!entry.isDirectory()) {
484 // if the entry is a file, extracts it
485 extractFile(zipIn, filePath);
487 // if the entry is a directory, make the directory
488 File dir = new File(filePath);
492 entry = zipIn.getNextEntry();
498 * Extracts a zip entry (file entry)
502 * @throws IOException
504 private static final int BUFFER_SIZE = 4096;
506 private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
507 //BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
508 try (FileOutputStream fos = new FileOutputStream(filePath);
509 BufferedOutputStream bos = new BufferedOutputStream(fos);) {
510 byte[] bytesIn = new byte[BUFFER_SIZE];
512 while ((read = zipIn.read(bytesIn)) != -1) {
513 bos.write(bytesIn, 0, read);
522 from toscaparser.common.exception import ValidationIssueCollector
523 from toscaparser.common.exception import URLException
524 from toscaparser.common.exception import ValidationError
525 from toscaparser.imports import ImportsLoader
526 from toscaparser.utils.gettextutils import _
527 from toscaparser.utils.urlutils import UrlUtils
530 from BytesIO import BytesIO
531 except ImportError: # Python 3.x
532 from io import BytesIO
537 def __init__(self, csar_file, a_file=True):
538 self.path = csar_file
540 self.is_validated = False
541 self.error_caught = False
546 """Validate the provided CSAR file."""
548 self.is_validated = True
550 # validate that the file or URL exists
551 missing_err_msg = (_('"%s" does not exist.') % self.path)
553 if not os.path.isfile(self.path):
554 ValidationIssueCollector.appendException(
555 ValidationError(message=missing_err_msg))
558 self.csar = self.path
560 if not UrlUtils.validate_url(self.path):
561 ValidationIssueCollector.appendException(
562 ValidationError(message=missing_err_msg))
565 response = requests.get(self.path)
566 self.csar = BytesIO(response.content)
568 # validate that it is a valid zip file
569 if not zipfile.is_zipfile(self.csar):
570 err_msg = (_('"%s" is not a valid zip file.') % self.path)
571 ValidationIssueCollector.appendException(
572 ValidationError(message=err_msg))
575 # validate that it contains the metadata file in the correct location
576 self.zfile = zipfile.ZipFile(self.csar, 'r')
577 filelist = self.zfile.namelist()
578 if 'TOSCA-Metadata/TOSCA.meta' not in filelist:
579 err_msg = (_('"%s" is not a valid CSAR as it does not contain the '
580 'required file "TOSCA.meta" in the folder '
581 '"TOSCA-Metadata".') % self.path)
582 ValidationIssueCollector.appendException(
583 ValidationError(message=err_msg))
586 # validate that 'Entry-Definitions' property exists in TOSCA.meta
587 data = self.zfile.read('TOSCA-Metadata/TOSCA.meta')
588 invalid_yaml_err_msg = (_('The file "TOSCA-Metadata/TOSCA.meta" in '
589 'the CSAR "%s" does not contain valid YAML '
590 'content.') % self.path)
592 meta = yaml.load(data)
593 if type(meta) is dict:
596 ValidationIssueCollector.appendException(
597 ValidationError(message=invalid_yaml_err_msg))
599 except yaml.YAMLError:
600 ValidationIssueCollector.appendException(
601 ValidationError(message=invalid_yaml_err_msg))
604 if 'Entry-Definitions' not in self.metadata:
605 err_msg = (_('The CSAR "%s" is missing the required metadata '
606 '"Entry-Definitions" in '
607 '"TOSCA-Metadata/TOSCA.meta".')
609 ValidationIssueCollector.appendException(
610 ValidationError(message=err_msg))
613 # validate that 'Entry-Definitions' metadata value points to an
614 # existing file in the CSAR
615 entry = self.metadata.get('Entry-Definitions')
616 if entry and entry not in filelist:
617 err_msg = (_('The "Entry-Definitions" file defined in the '
618 'CSAR "%s" does not exist.') % self.path)
619 ValidationIssueCollector.appendException(
620 ValidationError(message=err_msg))
623 # validate that external references in the main template actually
624 # exist and are accessible
625 self._validate_external_references()
626 return not self.error_caught
628 def get_metadata(self):
629 """Return the metadata dictionary."""
631 # validate the csar if not already validated
632 if not self.is_validated:
635 # return a copy to avoid changes overwrite the original
636 return dict(self.metadata) if self.metadata else None
638 def _get_metadata(self, key):
639 if not self.is_validated:
641 return self.metadata.get(key)
643 def get_author(self):
644 return self._get_metadata('Created-By')
646 def get_version(self):
647 return self._get_metadata('CSAR-Version')
649 def get_main_template(self):
650 entry_def = self._get_metadata('Entry-Definitions')
651 if entry_def in self.zfile.namelist():
654 def get_main_template_yaml(self):
655 main_template = self.get_main_template()
657 data = self.zfile.read(main_template)
658 invalid_tosca_yaml_err_msg = (
659 _('The file "%(template)s" in the CSAR "%(csar)s" does not '
660 'contain valid TOSCA YAML content.') %
661 {'template': main_template, 'csar': self.path})
663 tosca_yaml = yaml.load(data)
664 if type(tosca_yaml) is not dict:
665 ValidationIssueCollector.appendException(
666 ValidationError(message=invalid_tosca_yaml_err_msg))
669 ValidationIssueCollector.appendException(
670 ValidationError(message=invalid_tosca_yaml_err_msg))
672 def get_description(self):
673 desc = self._get_metadata('Description')
677 self.metadata['Description'] = \
678 self.get_main_template_yaml().get('description')
679 return self.metadata['Description']
681 def decompress(self):
682 if not self.is_validated:
684 self.temp_dir = tempfile.NamedTemporaryFile().name
685 with zipfile.ZipFile(self.csar, "r") as zf:
686 zf.extractall(self.temp_dir)
688 def _validate_external_references(self):
689 """Extracts files referenced in the main template
691 These references are currently supported:
693 * interface implementations
698 main_tpl_file = self.get_main_template()
699 if not main_tpl_file:
701 main_tpl = self.get_main_template_yaml()
703 if 'imports' in main_tpl:
704 ImportsLoader(main_tpl['imports'],
705 os.path.join(self.temp_dir, main_tpl_file))
707 if 'topology_template' in main_tpl:
708 topology_template = main_tpl['topology_template']
710 if 'node_templates' in topology_template:
711 node_templates = topology_template['node_templates']
713 for node_template_key in node_templates:
714 node_template = node_templates[node_template_key]
715 if 'artifacts' in node_template:
716 artifacts = node_template['artifacts']
717 for artifact_key in artifacts:
718 artifact = artifacts[artifact_key]
719 if isinstance(artifact, six.string_types):
720 self._validate_external_reference(
723 elif isinstance(artifact, dict):
724 if 'file' in artifact:
725 self._validate_external_reference(
729 ValidationIssueCollector.appendException(
730 ValueError(_('Unexpected artifact '
731 'definition for "%s".')
733 self.error_caught = True
734 if 'interfaces' in node_template:
735 interfaces = node_template['interfaces']
736 for interface_key in interfaces:
737 interface = interfaces[interface_key]
738 for opertation_key in interface:
739 operation = interface[opertation_key]
740 if isinstance(operation, six.string_types):
741 self._validate_external_reference(
745 elif isinstance(operation, dict):
746 if 'implementation' in operation:
747 self._validate_external_reference(
749 operation['implementation'])
752 shutil.rmtree(self.temp_dir)
754 def _validate_external_reference(self, tpl_file, resource_file,
756 """Verify that the external resource exists
758 If resource_file is a URL verify that the URL is valid.
759 If resource_file is a relative path verify that the path is valid
760 considering base folder (self.temp_dir) and tpl_file.
761 Note that in a CSAR resource_file cannot be an absolute path.
763 if UrlUtils.validate_url(resource_file):
764 msg = (_('The resource at "%s" cannot be accessed.') %
767 if UrlUtils.url_accessible(resource_file):
770 ValidationIssueCollector.appendException(
771 URLException(what=msg))
772 self.error_caught = True
774 ValidationIssueCollector.appendException(
775 URLException(what=msg))
776 self.error_caught = True
778 if os.path.isfile(os.path.join(self.temp_dir,
779 os.path.dirname(tpl_file),
784 ValidationIssueCollector.appendException(
785 ValueError(_('The resource "%s" does not exist.')
787 self.error_caught = True