package alien4cloud.tosca.parser; import java.io.InputStream; import java.nio.file.Path; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.Resource; import org.springframework.stereotype.Component; import org.yaml.snakeyaml.error.Mark; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.ScalarNode; import com.google.common.collect.Maps; import alien4cloud.tosca.context.ToscaContextual; import alien4cloud.tosca.model.ArchiveRoot; import alien4cloud.tosca.parser.impl.ErrorCode; import alien4cloud.tosca.parser.mapping.generator.MappingGenerator; import alien4cloud.tosca.parser.postprocess.ArchiveRootPostProcessor; /** * Main entry point for TOSCA template parsing. */ @Component public class ToscaParser extends YamlParser<ArchiveRoot> { private static final String DEFINITION_TYPE = "definition"; private Map<String, Map<String, INodeParser>> parserRegistriesByVersion = Maps.newHashMap(); @Resource private MappingGenerator mappingGenerator; @Resource private ArchiveRootPostProcessor archiveRootPostProcessor; @PostConstruct public void initialize() throws ParsingException { // initialize type registry for working draft 3. Map<String, INodeParser> registry = mappingGenerator.process("classpath:tosca-simple-profile-wd03-mapping.yml"); parserRegistriesByVersion.put("tosca_simple_yaml_1_0_0_wd03", registry); registry = mappingGenerator.process("classpath:alien-dsl-1.1.0-mapping.yml"); parserRegistriesByVersion.put("alien_dsl_1_1_0", registry); registry = mappingGenerator.process("classpath:alien-dsl-1.2.0-mapping.yml"); parserRegistriesByVersion.put("alien_dsl_1_2_0", registry); registry = mappingGenerator.process("classpath:alien-dsl-1.3.0-mapping.yml"); parserRegistriesByVersion.put("alien_dsl_1_3_0", registry); // experimental registry = mappingGenerator.process("classpath:tosca_simple_yaml_1_0.yml"); parserRegistriesByVersion.put("tosca_simple_yaml_1_0", registry); parserRegistriesByVersion.put("http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0", registry); } @Override @ToscaContextual public ParsingResult<ArchiveRoot> parseFile(String filePath, String fileName, InputStream yamlStream, ArchiveRoot instance) throws ParsingException { return super.parseFile(filePath, fileName, yamlStream, instance); } @Override @ToscaContextual public ParsingResult<ArchiveRoot> parseFile(Path yamlPath) throws ParsingException { return super.parseFile(yamlPath); } @Override @ToscaContextual public ParsingResult<ArchiveRoot> parseFile(Path yamlPath, ArchiveRoot instance) throws ParsingException { return super.parseFile(yamlPath, instance); } @Override protected void postParsing(ArchiveRoot result) { // Perform post processing model manipulation and validations. archiveRootPostProcessor.process(result); } @Override protected INodeParser<ArchiveRoot> getParser(Node rootNode, ParsingContextExecution context) throws ParsingException { if (rootNode instanceof MappingNode) { // try to find the tosca version DefinitionVersionInfo definitionVersionInfo = getToscaDefinitionVersion(((MappingNode) rootNode).getValue(), context); // call the parser for the given tosca version Map<String, INodeParser> registry = parserRegistriesByVersion.get(definitionVersionInfo.definitionVersion); if (registry == null) { throw new ParsingException(context.getFileName(), new ParsingError(ErrorCode.UNKNOWN_TOSCA_VERSION, "Definition version is not supported", definitionVersionInfo.definitionVersionTuple.getKeyNode().getStartMark(), "Version is not supported by Alien4Cloud", definitionVersionInfo.definitionVersionTuple.getValueNode().getStartMark(), definitionVersionInfo.definitionVersion)); } context.setRegistry(registry); context.setDefinitionVersion(definitionVersionInfo.definitionVersion); return registry.get(DEFINITION_TYPE); } else { throw new ParsingException(null, new ParsingError(ErrorCode.SYNTAX_ERROR, "File is not a valid tosca definition file.", new Mark("root", 0, 0, 0, null, 0), "The provided yaml file doesn't follow the Top-level key definitions of a valid TOSCA Simple profile file.", new Mark("root", 0, 0, 0, null, 0), "TOSCA Definitions")); } } private DefinitionVersionInfo getToscaDefinitionVersion(List<NodeTuple> topLevelNodes, ParsingContextExecution context) throws ParsingException { boolean first = true; for (NodeTuple node : topLevelNodes) { Node key = node.getKeyNode(); if (key instanceof ScalarNode) { ScalarNode scalarKey = (ScalarNode) key; if (scalarKey.getValue().equals("tosca_definitions_version")) { if (!first) { // TOSCA definition version must be the first yaml element context.getParsingErrors() .add(new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.TOSCA_VERSION_NOT_FIRST, "File is not a valid tosca definition file.", node.getKeyNode().getStartMark(), "tosca_definitions_version must be the first element of the document.", node.getValueNode().getEndMark(), null)); } return new DefinitionVersionInfo(ParserUtils.getScalar(node.getValueNode(), context), node); } } first = false; } throw new ParsingException(null, new ParsingError(ErrorCode.MISSING_TOSCA_VERSION, "File is not a valid tosca definition file.", new Mark("root", 0, 0, 0, null, 0), "Unable to find the mandatory tosca_definitions_version.", new Mark("root", 0, 0, 0, null, 0), null)); } private class DefinitionVersionInfo { private final String definitionVersion; private final NodeTuple definitionVersionTuple; public DefinitionVersionInfo(String definitionVersion, NodeTuple definitionVersionTuple) { this.definitionVersion = definitionVersion; this.definitionVersionTuple = definitionVersionTuple; } } }