package alien4cloud.rest.application; import java.util.List; import java.util.Map; import javax.annotation.Resource; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.google.common.collect.Lists; 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.dao.model.GetMultipleDataResult; import alien4cloud.exception.ApplicationVersionNotFoundException; import alien4cloud.exception.DeleteLastApplicationEnvironmentException; import alien4cloud.model.application.Application; import alien4cloud.model.application.ApplicationEnvironment; import alien4cloud.model.application.ApplicationVersion; import alien4cloud.paas.exception.OrchestratorDisabledException; import alien4cloud.paas.model.DeploymentStatus; import alien4cloud.rest.application.model.ApplicationEnvironmentDTO; import alien4cloud.rest.application.model.ApplicationEnvironmentRequest; import alien4cloud.rest.application.model.UpdateApplicationEnvironmentRequest; 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.ApplicationEnvironmentRole; import alien4cloud.security.model.ApplicationRole; import alien4cloud.utils.MapUtil; import alien4cloud.utils.ReflectionUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @Slf4j @RestController @RequestMapping({"/rest/applications/{applicationId:.+}/environments", "/rest/v1/applications/{applicationId:.+}/environments", "/rest/latest/applications/{applicationId:.+}/environments"}) @Api(value = "", description = "Manages application's environments") public class ApplicationEnvironmentController { @Resource(name = "alien-es-dao") private IGenericSearchDAO alienDAO; @Resource private ApplicationEnvironmentService applicationEnvironmentService; @Resource private ApplicationService applicationService; @Resource private ApplicationVersionService applicationVersionService; /** * Search for application environment for a given application id * * @param applicationId the targeted application id * @param searchRequest * @return A rest response that contains a {@link FacetedSearchResult} containing application environments for an application id */ @ApiOperation(value = "Search for application environments", notes = "Returns a search result with that contains application environments DTO matching the request. A application environment is returned only if the connected user has at least one application environment role in [ APPLICATION_USER | DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/search", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<GetMultipleDataResult<ApplicationEnvironmentDTO>> search(@PathVariable String applicationId, @RequestBody SearchRequest searchRequest) { FilterBuilder authorizationFilter = getEnvironmentAuthorizationFilters(applicationId); Map<String, String[]> applicationEnvironmentFilters = getApplicationEnvironmentFilters(applicationId); GetMultipleDataResult<ApplicationEnvironment> searchResult = alienDAO.search(ApplicationEnvironment.class, searchRequest.getQuery(), applicationEnvironmentFilters, authorizationFilter, null, searchRequest.getFrom(), searchRequest.getSize()); GetMultipleDataResult<ApplicationEnvironmentDTO> searchResultDTO = new GetMultipleDataResult<ApplicationEnvironmentDTO>(); searchResultDTO.setQueryDuration(searchResult.getQueryDuration()); searchResultDTO.setTypes(searchResult.getTypes()); searchResultDTO.setData(getApplicationEnvironmentDTO(searchResult.getData())); searchResultDTO.setTotalResults(searchResult.getTotalResults()); return RestResponseBuilder.<GetMultipleDataResult<ApplicationEnvironmentDTO>> builder().data(searchResultDTO).build(); } private FilterBuilder getEnvironmentAuthorizationFilters(String applicationId) { Application application = applicationService.checkAndGetApplication(applicationId); if (AuthorizationUtil.hasAuthorizationForApplication(application)) { return null; } return AuthorizationUtil.getResourceAuthorizationFilters(); } /** * Get application environment from its id * * @param applicationId The application id * @param applicationEnvironmentId the environment for which to get the status * @return A {@link RestResponse} that contains the application environment {@link ApplicationEnvironment}. */ @ApiOperation(value = "Get an application environment from its id", notes = "Returns the application environment. Roles required: Application environment [ APPLICATION_USER | DEPLOYMENT_MANAGER ], or application [APPLICATION_MANAGER]") @RequestMapping(value = "/{applicationEnvironmentId:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<ApplicationEnvironment> getApplicationEnvironment(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { Application application = applicationService.checkAndGetApplication(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getOrFail(applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment, ApplicationEnvironmentRole.values()); return RestResponseBuilder.<ApplicationEnvironment> builder().data(environment).build(); } /** * Get the current status of the environment for the given application. * * @param applicationId the id of the application to be deployed. * @param applicationEnvironmentId the environment for which to get the status * @return A {@link RestResponse} that contains the application's current {@link DeploymentStatus}. * @throws Exception */ @ApiOperation(value = "Get an application environment from its id", notes = "Returns the application environment. Application role required [ APPLICATION_USER | DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationEnvironmentId:.+}/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<DeploymentStatus> getApplicationEnvironmentStatus(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) throws Exception { Application application = applicationService.checkAndGetApplication(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getOrFail(applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment, ApplicationEnvironmentRole.values()); DeploymentStatus status = applicationEnvironmentService.getStatus(environment); return RestResponseBuilder.<DeploymentStatus> builder().data(status).build(); } /** * Create the application environment for an application * * @param request data to create an application environment * @return application environment id */ @ApiOperation(value = "Create a new application environment", notes = "If successfull returns a rest response with the id of the created application environment in data. If not successful a rest response with an error content is returned. Role required [ APPLICATIONS_MANAGER ]" + "By default the application environment 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(@PathVariable String applicationId, @RequestBody ApplicationEnvironmentRequest request) throws OrchestratorDisabledException { // User should be APPLICATION_MANAGER to create an environment applicationService.checkAndGetApplication(applicationId, ApplicationRole.APPLICATION_MANAGER); final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); ApplicationEnvironment appEnvironment = applicationEnvironmentService.createApplicationEnvironment(auth.getName(), applicationId, request.getName(), request.getDescription(), request.getEnvironmentType(), request.getVersionId()); alienDAO.save(appEnvironment); return RestResponseBuilder.<String> builder().data(appEnvironment.getId()).build(); } /** * Update application environment * * @param applicationEnvironmentId * @param request * @return */ @ApiOperation(value = "Updates by merging the given request into the given application environment", notes = "The logged-in user must have the application manager role for this application. Application role required [ APPLICATION_MANAGER ]") @RequestMapping(value = "/{applicationEnvironmentId:.+}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> update(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId, @RequestBody UpdateApplicationEnvironmentRequest request) throws OrchestratorDisabledException { // Only APPLICATION_MANAGER on the underlying application can update an application environment ApplicationEnvironment applicationEnvironment = applicationEnvironmentService.checkAndGetApplicationEnvironment(applicationEnvironmentId, ApplicationRole.APPLICATION_MANAGER); if (applicationEnvironment == null) { return RestResponseBuilder.<Void> builder().data(null).error(RestErrorBuilder.builder(RestErrorCode.APPLICATION_ENVIRONMENT_ERROR) .message("Application environment with id <" + applicationEnvironmentId + "> does not exist").build()).build(); } applicationEnvironmentService.ensureNameUnicity(applicationEnvironment.getApplicationId(), request.getName()); ReflectionUtil.mergeObject(request, applicationEnvironment); if (applicationEnvironment.getName() == null || applicationEnvironment.getName().isEmpty()) { throw new UnsupportedOperationException("Application environment name cannot be set to null or empty"); } alienDAO.save(applicationEnvironment); return RestResponseBuilder.<Void> builder().build(); } /** * Delete an application environment based on it's id. * * @param applicationEnvironmentId * @return */ @ApiOperation(value = "Delete an application environment from its id", notes = "The logged-in user must have the application manager role for this application. Application role required [ APPLICATION_MANAGER ]") @RequestMapping(value = "/{applicationEnvironmentId:.+}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Boolean> delete(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { // Only APPLICATION_MANAGER on the underlying application can delete an application environment ApplicationEnvironment environmentToDelete = applicationEnvironmentService.checkAndGetApplicationEnvironment(applicationEnvironmentId, ApplicationRole.APPLICATION_MANAGER); int countEnvironment = applicationEnvironmentService.getByApplicationId(environmentToDelete.getApplicationId()).length; if (countEnvironment == 1) { throw new DeleteLastApplicationEnvironmentException("Application environment with id <" + applicationEnvironmentId + "> cannot be deleted as it's the last one for the application id <" + environmentToDelete.getApplicationId() + ">"); } applicationEnvironmentService.delete(applicationEnvironmentId); return RestResponseBuilder.<Boolean> builder().data(true).build(); } /** * Filter to search app environments only for an application id * * @param applicationId * @return */ private Map<String, String[]> getApplicationEnvironmentFilters(String applicationId) { List<String> filterKeys = Lists.newArrayList(); List<String[]> filterValues = Lists.newArrayList(); if (applicationId != null) { filterKeys.add("applicationId"); filterValues.add(new String[] { applicationId }); } return MapUtil.newHashMap(filterKeys.toArray(new String[filterKeys.size()]), filterValues.toArray(new String[filterValues.size()][])); } /** * Get a list a application environment DTO * * @param applicationEnvironments * @return */ private ApplicationEnvironmentDTO[] getApplicationEnvironmentDTO(ApplicationEnvironment[] applicationEnvironments) { List<ApplicationEnvironmentDTO> listApplicationEnvironmentsDTO = Lists.newArrayList(); ApplicationEnvironmentDTO tempEnvDTO = null; for (ApplicationEnvironment env : applicationEnvironments) { tempEnvDTO = new ApplicationEnvironmentDTO(); tempEnvDTO.setApplicationId(env.getApplicationId()); tempEnvDTO.setDescription(env.getDescription()); tempEnvDTO.setEnvironmentType(env.getEnvironmentType()); tempEnvDTO.setId(env.getId()); tempEnvDTO.setName(env.getName()); tempEnvDTO.setUserRoles(env.getUserRoles()); tempEnvDTO.setGroupRoles(env.getGroupRoles()); ApplicationVersion applicationVersion = applicationVersionService.get(env.getCurrentVersionId()); tempEnvDTO.setCurrentVersionName(applicationVersion != null ? applicationVersion.getVersion() : null); try { tempEnvDTO.setStatus(applicationEnvironmentService.getStatus(env)); } catch (Exception e) { log.debug("Getting status for the environment <" + env.getId() + "> failed because the associated orchestrator cannot be reached. Returned status is UNKNOWN.", e); tempEnvDTO.setStatus(DeploymentStatus.UNKNOWN); } listApplicationEnvironmentsDTO.add(tempEnvDTO); } return listApplicationEnvironmentsDTO.toArray(new ApplicationEnvironmentDTO[listApplicationEnvironmentsDTO.size()]); } @ApiOperation(value = "Get the id of the topology linked to the environment", notes = "Application role required [ APPLICATION_MANAGER | APPLICATION_DEVOPS ]") @RequestMapping(value = "/{applicationEnvironmentId:.+}/topology", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<String> getTopologyId(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { Application application = applicationService.getOrFail(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getOrFail(applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment, ApplicationEnvironmentRole.values()); String topologyId = applicationEnvironmentService.getTopologyId(applicationEnvironmentId); if (topologyId == null) { throw new ApplicationVersionNotFoundException("An application version is required by an application environment."); } return RestResponseBuilder.<String> builder().data(topologyId).build(); } }