package alien4cloud.csar.services;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import alien4cloud.component.repository.exception.ToscaTypeAlreadyDefinedInOtherCSAR;
import org.alien4cloud.tosca.catalog.ArchiveUploadService;
import org.alien4cloud.tosca.catalog.index.CsarService;
import org.alien4cloud.tosca.model.CSARDependency;
import org.alien4cloud.tosca.model.Csar;
import org.eclipse.jgit.api.Git;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import com.google.common.collect.Lists;
import alien4cloud.common.AlienConstants;
import alien4cloud.component.repository.exception.CSARUsedInActiveDeployment;
import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.exception.AlreadyExistException;
import alien4cloud.exception.GitException;
import alien4cloud.git.RepositoryManager;
import alien4cloud.model.components.CSARSource;
import alien4cloud.model.git.CsarDependenciesBean;
import alien4cloud.model.git.CsarGitCheckoutLocation;
import alien4cloud.model.git.CsarGitRepository;
import alien4cloud.tosca.parser.ParsingException;
import alien4cloud.tosca.parser.ParsingResult;
import alien4cloud.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class CsarGitService {
@Inject
private CsarGitRepositoryService csarGitRepositoryService;
@Inject
private CsarFinderService csarFinderService;
@Inject
private ArchiveUploadService uploadService;
@Resource(name = "alien-es-dao")
private IGenericSearchDAO alienDAO;
@Resource
private CsarService csarService;
// TODO store archives that are not 'temp' in another location.
private Path tempDirPath;
private Path tempZipDirPath;
@Required
@Value("${directories.alien}/${directories.upload_temp}")
public void setTempDirPath(String tempDirPath) throws IOException {
this.tempDirPath = FileUtil.createDirectoryIfNotExists(tempDirPath + "/git");
this.tempZipDirPath = FileUtil.createDirectoryIfNotExists(tempDirPath + "/gitzips");
}
/**
* Delete an CsarGitRepository based on its id.
*
* @param id The id of the CsarGitRepository to delete.
*/
public void delete(String id) {
CsarGitRepository csarGit = csarGitRepositoryService.getOrFail(id);
if (csarGit.isStoredLocally()) {
Path repositoryPath = tempDirPath.resolve(csarGit.getId());
if (Files.isDirectory(repositoryPath)) {
FileSystemUtils.deleteRecursively(repositoryPath.toFile());
}
}
alienDAO.delete(CsarGitRepository.class, csarGit.getId());
}
/**
* Import Cloud Service ARchives from a git repository.
*
* @param id The id to access the git repository.
* @return The result of the import (that may contains errors etc.)
*/
public List<ParsingResult<Csar>> importFromGitRepository(String id) {
CsarGitRepository csarGitRepository = csarGitRepositoryService.getOrFail(id);
List<ParsingResult<Csar>> results = Lists.newArrayList();
try {
// Iterate over locations (branches, folders etc.) and process the import
for (CsarGitCheckoutLocation csarGitCheckoutLocation : csarGitRepository.getImportLocations()) {
List<ParsingResult<Csar>> result = doImport(csarGitRepository, csarGitCheckoutLocation);
if (result != null) {
results.addAll(result);
}
}
} finally {
// cleanup
Path archiveZipRoot = tempZipDirPath.resolve(csarGitRepository.getId());
Path archiveGitRoot = tempDirPath.resolve(csarGitRepository.getId());
try {
FileUtil.delete(archiveZipRoot);
if (!csarGitRepository.isStoredLocally()) {
FileUtil.delete(archiveGitRoot);
}
} catch (IOException e) {
log.error("Failed to cleanup files after git import.", e);
}
}
return results;
}
private List<ParsingResult<Csar>> doImport(CsarGitRepository csarGitRepository, CsarGitCheckoutLocation csarGitCheckoutLocation) {
Git git = null;
try {
// checkout the repository branch
git = RepositoryManager.cloneOrCheckout(tempDirPath, csarGitRepository.getRepositoryUrl(), csarGitRepository.getUsername(),
csarGitRepository.getPassword(), csarGitCheckoutLocation.getBranchId(), csarGitRepository.getId());
// if the repository is persistent we also have to pull to get the latest version
if (csarGitRepository.isStoredLocally() && !RepositoryManager.isATag(git, csarGitCheckoutLocation.getBranchId())) {
// try to pull
RepositoryManager.pull(git, csarGitRepository.getUsername(), csarGitRepository.getPassword());
}
String hash = RepositoryManager.getLastHash(git);
// now that the repository is checked out and up to date process with the import
List<ParsingResult<Csar>> results = processImport(csarGitRepository, csarGitCheckoutLocation, hash);
if (!Objects.equals(csarGitCheckoutLocation.getLastImportedHash(), hash)) {
csarGitCheckoutLocation.setLastImportedHash(hash);
alienDAO.save(csarGitRepository); // update the hash for this location.
}
// TODO best would be to provide with a better result to show that we didn't retried import
return results;
} finally {
RepositoryManager.close(git);
}
}
private List<ParsingResult<Csar>> processImport(CsarGitRepository csarGitRepository, CsarGitCheckoutLocation csarGitCheckoutLocation, String gitHash) {
// find all the archives under the given hierarchy and zip them to create archives
Path archiveZipRoot = tempZipDirPath.resolve(csarGitRepository.getId());
Path archiveGitRoot = tempDirPath.resolve(csarGitRepository.getId());
Set<Path> archivePaths = csarFinderService.prepare(archiveGitRoot, archiveZipRoot, csarGitCheckoutLocation.getSubPath());
// TODO code review has to be completed to further cleanup below processing.
List<ParsingResult<Csar>> parsingResult = Lists.newArrayList();
try {
Map<CSARDependency, CsarDependenciesBean> csarDependenciesBeans = uploadService.preParsing(archivePaths, parsingResult);
List<CsarDependenciesBean> sorted = sort(csarDependenciesBeans);
for (CsarDependenciesBean csarBean : sorted) {
if (csarGitCheckoutLocation.getLastImportedHash() != null && csarGitCheckoutLocation.getLastImportedHash().equals(gitHash)) {
if (csarService.get(csarBean.getSelf().getName(), csarBean.getSelf().getVersion()) != null) {
// no commit since last import and the archive still exist in the repo, so do not import
// TODO notify the user that the archive has already been imported
continue;
}
}
// FIXME Add possibility to choose an workspace
ParsingResult<Csar> result = uploadService.upload(csarBean.getPath(), CSARSource.GIT, AlienConstants.GLOBAL_WORKSPACE_ID);
parsingResult.add(result);
}
return parsingResult;
} catch (ParsingException e) {
// TODO Actually add a parsing result with error.
throw new GitException("Failed to import archive from git as it cannot be parsed", e);
} catch (AlreadyExistException e) {
return parsingResult;
} catch (CSARUsedInActiveDeployment e) {
// TODO Actually add a parsing result with error.
return parsingResult;
} catch (ToscaTypeAlreadyDefinedInOtherCSAR e) {
// TODO Actually add a parsing result with error.
return parsingResult;
}
}
private List<CsarDependenciesBean> sort(Map<CSARDependency, CsarDependenciesBean> elements) {
List<CsarDependenciesBean> sortedCsars = Lists.newArrayList();
List<CsarDependenciesBean> independents = Lists.newArrayList();
for (Map.Entry<CSARDependency, CsarDependenciesBean> entry : elements.entrySet()) {
CsarDependenciesBean csar = entry.getValue();
if (csar.getDependencies() == null) {
// the element has no dependencies
independents.add(csar);
} else {
// complete the list of dependent elements
List<CSARDependency> toClears = Lists.newArrayList();
for (CSARDependency dependent : csar.getDependencies()) {
CsarDependenciesBean providedDependency = elements.get(dependent);
if (providedDependency == null) {
// remove the dependency as it may be in the alien repo
toClears.add(dependent);
} else {
providedDependency.getDependents().add(entry.getValue());
}
}
for (CSARDependency toClear : toClears) {
csar.getDependencies().remove(toClear);
}
if (csar.getDependencies().isEmpty()) {
independents.add(csar);
}
}
}
while (independents.size() > 0) {
CsarDependenciesBean independent = independents.remove(0);
elements.remove(independent.getSelf()); // remove from the elements
sortedCsars.add(independent); // element has no more dependencies
for (CsarDependenciesBean dependent : independent.getDependents()) {
dependent.getDependencies().remove(independent.getSelf());
if (dependent.getDependencies().isEmpty()) {
independents.add(dependent);
}
}
}
if (elements.size() > 0) {
// TODO there is looping dependencies throw exception or ignore ?
}
return sortedCsars;
}
}