package alien4cloud.csar.services; import java.io.*; import java.nio.charset.Charset; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Set; import org.springframework.stereotype.Service; import com.google.common.base.Strings; import com.google.common.collect.Sets; import alien4cloud.exception.GitException; import alien4cloud.tosca.parser.ToscaArchiveParser; import alien4cloud.utils.FileUtil; import lombok.SneakyThrows; /** * This service detects TOSCA cloud service archives in a given folder and return an ordered list of archives path to import. */ @Service public class CsarFinderService { /** * Search in the given path for folders that contains CloudServiceArchives and zip them so they. * * @param searchPath The path in which to search for archives. * @return a list of path that contains archives. */ public Set<Path> prepare(Path searchPath, Path zipPath, String subpath) { ToscaFinderWalker toscaFinderWalker = new ToscaFinderWalker(); toscaFinderWalker.zipRootPath = zipPath; toscaFinderWalker.rootPath = searchPath; toscaFinderWalker.subpath = subpath; try { Files.walkFileTree(searchPath, toscaFinderWalker); } catch (IOException e) { throw new GitException("Failed to browse git repository content in order to import archives.", e); } return toscaFinderWalker.toscaArchives; } private static class ToscaFinderWalker extends SimpleFileVisitor<Path> { private Path rootPath; private Path zipRootPath; private String subpath; private Set<Path> toscaArchives = Sets.newHashSet(); @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (ToscaArchiveParser.TOSCA_META_FOLDER_NAME.equals(dir.getFileName())) { // zip parent folder and add the path. addToscaArchive(dir.getParent()); return FileVisitResult.SKIP_SIBLINGS; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (isToscaFile(file)) { addToscaArchive(file.getParent()); return FileVisitResult.SKIP_SIBLINGS; } return FileVisitResult.CONTINUE; } private void addToscaArchive(Path path) { if (!(Strings.isNullOrEmpty(subpath) || path.endsWith(subpath))) { return; } Path relativePath = rootPath.relativize(path); Path zipPath = zipRootPath.resolve(relativePath).resolve("archive.zip"); try { if (Files.exists(zipPath)) { FileUtil.delete(zipPath); } FileUtil.zip(path, zipPath); toscaArchives.add(zipPath); } catch (IOException e) { throw new GitException("Failed to zip archives in order to import them.", e); } } @SneakyThrows private boolean isToscaFile(Path path) { if (isYamlFile(path.getFileName()) && readFirstLine(path).startsWith("tosca_definitions_version")) { return true; } return false; } private boolean isYamlFile(Path fileName) { File file = new File(fileName.toString()); if (file.isFile() && fileName.toString().endsWith(".yaml") || fileName.toString().endsWith(".yml")) { return true; } return false; } /** * Read the first non-empty line of the file * * @param path to the file * @return the first non-empty line of the file or an empty string * @throws IOException */ private static String readFirstLine(Path path) throws IOException { InputStreamReader stream = new InputStreamReader(Files.newInputStream(path), Charset.defaultCharset()); try (BufferedReader reader = new BufferedReader(stream)) { String line = reader.readLine(); while (line != null) { line = line.trim(); if (line.length() > 0) { return line; } line = reader.readLine(); } } return ""; } } }