package alien4cloud.rest.csar; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Set; import javax.annotation.Resource; import javax.validation.Valid; import org.alien4cloud.tosca.catalog.ArchiveUploadService; import org.alien4cloud.tosca.catalog.index.CsarService; import org.alien4cloud.tosca.catalog.index.IArchiveIndexerAuthorizationFilter; import org.alien4cloud.tosca.catalog.index.ICsarAuthorizationFilter; import org.alien4cloud.tosca.catalog.index.ICsarSearchService; import org.alien4cloud.tosca.catalog.repository.CsarFileRepository; import org.alien4cloud.tosca.model.CSARDependency; import org.alien4cloud.tosca.model.Csar; import org.apache.commons.collections4.CollectionUtils; import org.elasticsearch.common.collect.Sets; import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.google.common.collect.Lists; import alien4cloud.audit.annotation.Audit; import alien4cloud.common.AlienConstants; import alien4cloud.component.repository.exception.CSARUsedInActiveDeployment; import alien4cloud.component.repository.exception.ToscaTypeAlreadyDefinedInOtherCSAR; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.dao.model.FacetedSearchResult; import alien4cloud.exception.AlreadyExistException; import alien4cloud.model.common.Usage; import alien4cloud.model.components.CSARSource; import alien4cloud.rest.component.SearchRequest; import alien4cloud.rest.model.RestError; import alien4cloud.rest.model.RestErrorBuilder; import alien4cloud.rest.model.RestErrorCode; import alien4cloud.rest.model.RestResponse; import alien4cloud.rest.model.RestResponseBuilder; import alien4cloud.tosca.parser.ParsingError; import alien4cloud.tosca.parser.ParsingErrorLevel; import alien4cloud.tosca.parser.ParsingException; import alien4cloud.tosca.parser.ParsingResult; import alien4cloud.tosca.parser.impl.ErrorCode; import alien4cloud.utils.FileUploadUtil; import alien4cloud.utils.FileUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @RestController @RequestMapping({ "/rest/csars", "/rest/v1/csars", "/rest/latest/csars" }) @Api(value = "", description = "Operations on CSARs") @Slf4j public class CloudServiceArchiveController { @Resource private ArchiveUploadService csarUploadService; @Resource(name = "alien-es-dao") private IGenericSearchDAO csarDAO; @Resource private ICsarSearchService csarSearchService; @Resource private CsarService csarService; @Resource private ICsarAuthorizationFilter csarAuthorizationFilter; @Resource private IArchiveIndexerAuthorizationFilter archiveIndexerAuthorizationFilter; private Path tempDirPath; @ApiOperation(value = "Upload a csar zip file.") @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<CsarUploadResult> uploadCSAR(@RequestParam(required = false) String workspace, @RequestParam("file") MultipartFile csar) throws IOException { Path csarPath = null; try { if (workspace == null) { workspace = AlienConstants.GLOBAL_WORKSPACE_ID; } // Perform check that the user has one of ARCHITECT, COMPONENT_MANAGER or ADMIN role archiveIndexerAuthorizationFilter.preCheckAuthorization(workspace); log.info("Serving file upload with name [" + csar.getOriginalFilename() + "]"); csarPath = Files.createTempFile(tempDirPath, null, '.' + CsarFileRepository.CSAR_EXTENSION); // save the archive in the temp directory FileUploadUtil.safeTransferTo(csarPath, csar); // load, parse the archive definitions and save on disk ParsingResult<Csar> result = csarUploadService.upload(csarPath, CSARSource.UPLOAD, workspace); RestError error = null; if (result.hasError(ParsingErrorLevel.ERROR)) { error = RestErrorBuilder.builder(RestErrorCode.CSAR_PARSING_ERROR).build(); } return RestResponseBuilder.<CsarUploadResult> builder().error(error).data(CsarUploadUtil.toUploadResult(result)).build(); } catch (ParsingException e) { log.error("Error happened while parsing csar file <" + e.getFileName() + ">", e); String fileName = e.getFileName() == null ? csar.getOriginalFilename() : e.getFileName(); CsarUploadResult uploadResult = new CsarUploadResult(); uploadResult.getErrors().put(fileName, e.getParsingErrors()); return RestResponseBuilder.<CsarUploadResult> builder().error(RestErrorBuilder.builder(RestErrorCode.CSAR_INVALID_ERROR).build()).data(uploadResult) .build(); } catch (AlreadyExistException e) { log.error("A CSAR with the same name and the same version already existed in the repository", e); CsarUploadResult uploadResult = new CsarUploadResult(); uploadResult.getErrors().put(csar.getOriginalFilename(), Lists.newArrayList(new ParsingError(ErrorCode.CSAR_ALREADY_EXISTS, "CSAR already exists", null, "Unable to override an existing CSAR if the version is not a SNAPSHOT version.", null, null))); return RestResponseBuilder.<CsarUploadResult> builder().error(RestErrorBuilder.builder(RestErrorCode.ALREADY_EXIST_ERROR).build()) .data(uploadResult).build(); } catch (CSARUsedInActiveDeployment e) { log.error("This csar is used in an active deployment. It cannot be overrided.", e); CsarUploadResult uploadResult = new CsarUploadResult(); uploadResult.getErrors().put(csar.getOriginalFilename(), Lists.newArrayList(new ParsingError(ErrorCode.CSAR_USED_IN_ACTIVE_DEPLOYMENT, "CSAR used in active deployment", null, "Unable to override a csar used in an active deployment.", null, null))); return RestResponseBuilder.<CsarUploadResult> builder().error(RestErrorBuilder.builder(RestErrorCode.RESOURCE_USED_ERROR).build()) .data(uploadResult).build(); } catch (ToscaTypeAlreadyDefinedInOtherCSAR e) { log.error("Skipping archive import, it's archive contain's a tosca type already defined in an other archive." + e.getMessage()); CsarUploadResult uploadResult = new CsarUploadResult(); uploadResult.getErrors().put(csar.getOriginalFilename(), Lists.newArrayList( new ParsingError(ErrorCode.TOSCA_TYPE_ALREADY_EXISTS_IN_OTHER_CSAR, "Tosca type conflict", null, e.getMessage(), null, null))); return RestResponseBuilder.<CsarUploadResult> builder().error(RestErrorBuilder.builder(RestErrorCode.ALREADY_EXIST_ERROR).build()) .data(uploadResult).build(); } finally { if (csarPath != null) { // Clean up try { FileUtil.delete(csarPath); } catch (IOException e) { // The repository might just move the file instead of copying to save IO disk access } } } } @ApiOperation(value = "Add dependency to the csar with given id.") @RequestMapping(value = "/{csarId:.+?}/dependencies", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit @Deprecated public RestResponse<Boolean> addDependency(@PathVariable String csarId, @Valid @RequestBody CSARDependency dependency) { Csar csar = csarService.getOrFail(csarId); csarAuthorizationFilter.checkWriteAccess(csar); Set<CSARDependency> existingDependencies = csar.getDependencies(); if (existingDependencies == null) { existingDependencies = Sets.newHashSet(); csar.setDependencies(existingDependencies); } boolean couldBeSaved = existingDependencies.add(dependency); csarDAO.save(csar); return RestResponseBuilder.<Boolean> builder().data(couldBeSaved).build(); } @ApiOperation(value = "Delete a CSAR given its id.") @RequestMapping(value = "/{csarId:.+?}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<List<Usage>> delete(@PathVariable String csarId) { Csar csar = csarService.getOrFail(csarId); csarAuthorizationFilter.checkWriteAccess(csar); List<Usage> relatedResourceList = csarService.deleteCsarWithElements(csar); if (CollectionUtils.isNotEmpty(relatedResourceList)) { String errorMessage = "The csar named <" + csar.getName() + "> in version <" + csar.getVersion() + "> can not be deleted since it is referenced by other resources"; return RestResponseBuilder.<List<Usage>> builder().data(relatedResourceList) .error(RestErrorBuilder.builder(RestErrorCode.DELETE_REFERENCED_OBJECT_ERROR).message(errorMessage).build()).build(); } return RestResponseBuilder.<List<Usage>> builder().build(); } @ApiOperation(value = "Get a CSAR given its id.", notes = "Returns a CSAR.") @RequestMapping(value = "/{csarId:.+?}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<CsarInfoDTO> read(@PathVariable String csarId) { Csar csar = csarService.get(csarId); csarAuthorizationFilter.checkReadAccess(csar); List<Usage> relatedResourceList = csarService.getCsarRelatedResourceList(csar); CsarInfoDTO csarInfo = new CsarInfoDTO(csar, relatedResourceList); return RestResponseBuilder.<CsarInfoDTO> builder().data(csarInfo).build(); } @ApiOperation(value = "Search for cloud service archives.") @RequestMapping(value = "/search", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<FacetedSearchResult> search(@RequestBody SearchRequest searchRequest) { return RestResponseBuilder.<FacetedSearchResult> builder() .data(csarSearchService.search(searchRequest.getQuery(), searchRequest.getFrom(), searchRequest.getSize(), searchRequest.getFilters())).build(); } @Required @Value("${directories.alien}/${directories.upload_temp}") public void setTempDirPath(String tempDirPath) throws IOException { this.tempDirPath = FileUtil.createDirectoryIfNotExists(tempDirPath); log.info("Temporary folder for upload was set to [" + this.tempDirPath + "]"); } }