package alien4cloud.rest.application; import java.io.IOException; import javax.annotation.Resource; import javax.validation.Valid; import alien4cloud.exception.AlreadyExistException; import org.alien4cloud.tosca.catalog.index.ArchiveIndexer; import org.elasticsearch.index.query.FilterBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import alien4cloud.application.ApplicationEnvironmentService; import alien4cloud.application.ApplicationService; import alien4cloud.application.ApplicationVersionService; import alien4cloud.audit.annotation.Audit; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.dao.model.FacetedSearchResult; import alien4cloud.exception.DeleteDeployedException; import alien4cloud.images.IImageDAO; import alien4cloud.images.exception.ImageUploadException; import alien4cloud.model.application.Application; import alien4cloud.model.application.ApplicationVersion; import alien4cloud.paas.exception.OrchestratorDisabledException; import alien4cloud.rest.application.model.CreateApplicationRequest; import alien4cloud.rest.application.model.UpdateApplicationRequest; import alien4cloud.rest.component.SearchRequest; import alien4cloud.rest.model.RestErrorBuilder; import alien4cloud.rest.model.RestErrorCode; import alien4cloud.rest.model.RestResponse; import alien4cloud.rest.model.RestResponseBuilder; import alien4cloud.security.AuthorizationUtil; import alien4cloud.security.model.ApplicationRole; import alien4cloud.security.model.Role; import alien4cloud.utils.VersionUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; /** * Service that allows managing applications. */ @Slf4j @RestController @RequestMapping({ "/rest/applications", "/rest/v1/applications", "/rest/latest/applications" }) @Api(value = "", description = "Operations on Applications") public class ApplicationController { @Resource private IImageDAO imageDAO; @Resource(name = "alien-es-dao") private IGenericSearchDAO alienDAO; @Resource ArchiveIndexer archiveIndexer; @Resource private ApplicationService applicationService; @Resource private ApplicationVersionService applicationVersionService; @Resource private ApplicationEnvironmentService applicationEnvironmentService; /** * Create a new application in the system. * * @param request The new application to create. */ @ApiOperation(value = "Create a new application in the system.", notes = "If successfull returns a rest response with the id of the created application in data. If not successful a rest response with an error content is returned. Role required [ APPLICATIONS_MANAGER ]. " + "By default the application creator will have application roles [APPLICATION_MANAGER, DEPLOYMENT_MANAGER]") @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.CREATED) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<String> create(@Valid @RequestBody CreateApplicationRequest request) { AuthorizationUtil.checkHasOneRoleIn(Role.APPLICATIONS_MANAGER); final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // check the topology template id to recover the related topology id String topologyId = request.getTopologyTemplateVersionId(); // check unity of archive name try { archiveIndexer.ensureUniqueness(request.getArchiveName(), VersionUtil.DEFAULT_VERSION_NAME); } catch (AlreadyExistException e) { return RestResponseBuilder.<String> builder().error( RestErrorBuilder.builder(RestErrorCode.APPLICATION_CSAR_VERSION_ALREADY_EXIST).message("CSAR: " + request.getArchiveName() + ", Version: " + VersionUtil.DEFAULT_VERSION_NAME + " already exists in the repository.").build()) .build(); } // create the application with default environment and version String applicationId = applicationService.create(auth.getName(), request.getArchiveName(), request.getName(), request.getDescription()); ApplicationVersion version = applicationVersionService.createApplicationVersion(applicationId, topologyId); applicationEnvironmentService.createApplicationEnvironment(auth.getName(), applicationId, version.getId()); return RestResponseBuilder.<String> builder().data(applicationId).build(); } /** * Get an application from it's id. * * @param applicationId The application id. */ @ApiOperation(value = "Get an application based from its id.", notes = "Returns the application details. Application role required [ APPLICATION_MANAGER | APPLICATION_USER | APPLICATION_DEVOPS | DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<Application> get(@PathVariable String applicationId) { return RestResponseBuilder.<Application> builder().data(applicationService.checkAndGetApplication(applicationId)).build(); } /** * Search for an application. * * @param searchRequest The element that contains criterias for search operation. * @return A rest response that contains a {@link FacetedSearchResult} containing applications. */ @ApiOperation(value = "Search for applications", notes = "Returns a search result with that contains applications matching the request. A application is returned only if the connected user has at least one application role in [ APPLICATION_MANAGER | APPLICATION_USER | APPLICATION_DEVOPS | DEPLOYMENT_MANAGER ]") @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) { FilterBuilder authorizationFilter = AuthorizationUtil.getResourceAuthorizationFilters(); FacetedSearchResult searchResult = alienDAO.facetedSearch(Application.class, searchRequest.getQuery(), searchRequest.getFilters(), authorizationFilter, null, searchRequest.getFrom(), searchRequest.getSize()); return RestResponseBuilder.<FacetedSearchResult> builder().data(searchResult).build(); } /** * Delete an application based on it's id. * * @param applicationId The id of the application to delete. * @return A rest response. */ @ApiOperation(value = "Delete an application from its id.", notes = "The logged-in user must have the application manager role for this application. Application role required [ APPLICATION_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Boolean> delete(@PathVariable String applicationId) { applicationService.checkAndGetApplication(applicationId, ApplicationRole.APPLICATION_MANAGER); try { boolean deleted = applicationService.delete(applicationId); if (!deleted) { throw new DeleteDeployedException( "Application with id <" + applicationId + "> cannot be deleted since one of its environment is still deployed."); } } catch (OrchestratorDisabledException e) { log.error("Failed to delete the application due to Cloud error", e); return RestResponseBuilder.<Boolean> builder().data(false).error(RestErrorBuilder.builder(RestErrorCode.CLOUD_DISABLED_ERROR) .message("Could not delete the application with id <" + applicationId + "> with error : " + e.getMessage()).build()).build(); } return RestResponseBuilder.<Boolean> builder().data(true).build(); } /** * Update application's image. * * @param applicationId The application id. * @param image new image of the application * @return nothing if success, error will be handled in global exception strategy */ @ApiOperation(value = "Updates the image for the application.", notes = "The logged-in user must have the application manager role for this application. Application role required [ APPLICATION_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}/image", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<String> updateImage(@PathVariable String applicationId, @RequestParam("file") MultipartFile image) { Application application = applicationService.checkAndGetApplication(applicationId, ApplicationRole.APPLICATION_MANAGER); String imageId; try { imageId = imageDAO.writeImage(image.getBytes()); } catch (IOException e) { throw new ImageUploadException( "Unable to read image from file upload [" + image.getOriginalFilename() + "] to update application [" + applicationId + "]", e); } application.setImageId(imageId); alienDAO.save(application); return RestResponseBuilder.<String> builder().data(imageId).build(); } /** * Update application's name * * @param applicationId The application id. * @return nothing if success, error will be handled in global exception strategy */ @ApiOperation(value = "Updates by merging the given request into the given application .", notes = "The logged-in user must have the application manager role for this application. Application role required [ APPLICATION_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> update(@PathVariable String applicationId, @RequestBody UpdateApplicationRequest request) { applicationService.update(applicationId, request.getName(), request.getDescription()); return RestResponseBuilder.<Void> builder().build(); } }