/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.builder; import org.eclipse.che.api.builder.dto.BaseBuilderRequest; import org.eclipse.che.api.builder.dto.BuildOptions; import org.eclipse.che.api.builder.dto.BuildTaskDescriptor; import org.eclipse.che.api.builder.dto.BuilderDescriptor; import org.eclipse.che.api.builder.internal.Constants; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.rest.HttpServletProxyResponse; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.Description; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.annotations.Valid; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.user.User; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; /** * RESTful frontend for BuildQueue. * * @author andrew00x * @author Eugene Voevodin */ @Api(value = "/builder", description = "Builder manager") @Path("/builder/{ws-id}") @Description("Builder API") public class BuilderService extends Service { private static final Logger LOG = LoggerFactory.getLogger(BuilderService.class); @Inject private BuildQueue buildQueue; @ApiOperation(value = "Build a project", notes = "Build a project. Optional build options are passed in a JSON", response = BuildTaskDescriptor.class, position = 1) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Server Error")}) @GenerateLink(rel = Constants.LINK_REL_BUILD) @POST @Path("/build") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public BuildTaskDescriptor build(@PathParam("ws-id") String workspace, @ApiParam(value = "Project name", required = true) @Required @Description("project name") @QueryParam("project") String project, @ApiParam( value = "Build options. Here you specify optional build options like skip tests, build targets etc.") @Description("build options") BuildOptions options) throws Exception { return buildQueue.scheduleBuild(workspace, project, getServiceContext(), options).getDescriptor(); } @ApiOperation(value = "Analyze dependencies", notes = "Analyze dependencies", response = BuildTaskDescriptor.class, position = 2) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Server Error")}) @GenerateLink(rel = Constants.LINK_REL_DEPENDENCIES_ANALYSIS) @POST @Path("/dependencies") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public BuildTaskDescriptor dependencies(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Project name", required = true) @Required @Description("project name") @QueryParam("project") String project, @ApiParam(value = "Analysis type. If dropped, list is used by default", defaultValue = "list", allowableValues = "copy,list, copy-sources") @Valid({"copy", "list"}) @DefaultValue("list") @QueryParam("type") String analyzeType, @ApiParam( value = "Build options. Here you specify optional build options like skip tests, " + "build targets etc.") @Description("build options") BuildOptions options) throws Exception { return buildQueue.scheduleDependenciesAnalyze(workspace, project, analyzeType, getServiceContext(), options).getDescriptor(); } @ApiOperation(value = "Get project build tasks", notes = "Get build tasks that are related to a particular project. User can see only own processes related to own projects.", response = BuildTaskDescriptor.class, responseContainer = "List", position = 3) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Server Error")}) @GET @Path("/builds") @Produces(MediaType.APPLICATION_JSON) public List<BuildTaskDescriptor> builds(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Project name", required = false) @Required @Description("project name") @QueryParam("project") String project) { // handle project name if (project != null && !project.startsWith("/")) { project = '/' + project; } final List<BuildTaskDescriptor> builds = new LinkedList<>(); final User user = EnvironmentContext.getCurrent().getUser(); if (user != null) { final String userName = user.getName(); for (BuildQueueTask task : buildQueue.getTasks()) { final BaseBuilderRequest request = task.getRequest(); if (request.getWorkspace().equals(workspace) && request.getProject().equals(project) && request.getUserId().equals(userName)) { try { builds.add(task.getDescriptor()); } catch (NotFoundException e) { // NotFoundException is possible and should not be treated as error in this case. Typically it occurs if slave // builder already cleaned up the task by its internal cleaner but BuildQueue doesn't re-check yet slave builder and // doesn't have actual info about state of slave builder. } catch (BuilderException e) { // Decide ignore such error to be able show maximum available info. LOG.error(e.getMessage(), e); } } } } return builds; } @ApiOperation(value = "Get build status", notes = "Get status of a specified build", response = BuildTaskDescriptor.class, position = 4) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GET @Path("/status/{id}") @Produces(MediaType.APPLICATION_JSON) public BuildTaskDescriptor getStatus(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Build ID", required = true) @PathParam("id") Long id) throws Exception { return buildQueue.getTask(id).getDescriptor(); } @ApiOperation(value = "Cancel build", notes = "Cancel build task", response = BuildTaskDescriptor.class, position = 5) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @POST @Path("/cancel/{id}") @Produces(MediaType.APPLICATION_JSON) public BuildTaskDescriptor cancel(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Build ID", required = true) @PathParam("id") Long id) throws Exception { final BuildQueueTask task = buildQueue.getTask(id); task.cancel(); return task.getDescriptor(); } @ApiOperation(value = "Get build logs", notes = "Get build logs", position = 5) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GET @Path("/logs/{id}") public void getLogs(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Get build logs", required = true) @PathParam("id") Long id, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream buildQueue.getTask(id).readLogs(new HttpServletProxyResponse(httpServletResponse)); } @ApiOperation(value = "Get build report", notes = "Get build report by build ID", position = 6) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GET @Path("/report/{id}") public void getReport(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Build ID", required = true) @PathParam("id") Long id, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream buildQueue.getTask(id).readReport(new HttpServletProxyResponse(httpServletResponse)); } private static final Pattern JSON_CONTENT_TYPE_PATTERN = Pattern.compile("^application/json(\\s*;.*)?$"); private static final Pattern HTML_CONTENT_TYPE_PATTERN = Pattern.compile("^text/htm(l)?(\\s*;.*)?$"); @GET @Path("browse/{id}") public void browseDirectory(@PathParam("ws-id") String workspace, @PathParam("id") Long id, @DefaultValue(".") @QueryParam("path") String path, @Context HttpServletResponse httpServletResponse) throws Exception { final BuildQueueTask myTask = buildQueue.getTask(id); final RemoteTask myRemoteTask = myTask.getRemoteTask(); if (myRemoteTask == null) { return; } final String myBaseUri = getServiceContext().getServiceUriBuilder().build(workspace).toString(); final String from = String.format("%s/(browse|download|view)/%s/%d", myRemoteTask.getBaseRemoteUrl(), myRemoteTask.getBuilder(), myRemoteTask.getId()); final String to = String.format("%s/$1/%d", myBaseUri, myTask.getId()); final List<Pair<String, String>> urlRewriteRules = new ArrayList<>(1); urlRewriteRules.add(Pair.of(from, to)); // Response write directly to the servlet request stream final HttpServletProxyResponse proxyResponse = new HttpServletProxyResponse(httpServletResponse, Collections.singletonMap( HTML_CONTENT_TYPE_PATTERN, urlRewriteRules)); myRemoteTask.browseDirectory(path, proxyResponse); } @GET @Path("/view/{id}") public void viewFile(@PathParam("ws-id") String workspace, @PathParam("id") Long id, @Required @QueryParam("path") String path, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream buildQueue.getTask(id).readFile(path, new HttpServletProxyResponse(httpServletResponse)); } @GET @Path("tree/{id}") public void listDirectory(@PathParam("ws-id") String workspace, @PathParam("id") Long id, @DefaultValue(".") @QueryParam("path") String path, @Context HttpServletResponse httpServletResponse) throws Exception { final BuildQueueTask myTask = buildQueue.getTask(id); final RemoteTask myRemoteTask = myTask.getRemoteTask(); if (myRemoteTask == null) { return; } final String myBaseUri = getServiceContext().getServiceUriBuilder().build(workspace).toString(); final String from = String.format("%s/(tree|download|view)/%s/%d", myRemoteTask.getBaseRemoteUrl(), myRemoteTask.getBuilder(), myRemoteTask.getId()); final String to = String.format("%s/$1/%d", myBaseUri, myTask.getId()); final List<Pair<String, String>> urlRewriteRules = new ArrayList<>(1); urlRewriteRules.add(Pair.of(from, to)); // Response write directly to the servlet request stream final HttpServletProxyResponse proxyResponse = new HttpServletProxyResponse(httpServletResponse, Collections.singletonMap( JSON_CONTENT_TYPE_PATTERN, urlRewriteRules)); myRemoteTask.listDirectory(path, proxyResponse); } @ApiOperation(value = "Download build artifact", notes = "Download build artifact", position = 7) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GET @Path("/download/{id}") public void downloadFile(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Build ID", required = true) @PathParam("id") Long id, @ApiParam(value = "Path to a build artifact as /target/{BuildArtifactName}", required = true) @Required @QueryParam("path") String path, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream buildQueue.getTask(id).downloadFile(path, new HttpServletProxyResponse(httpServletResponse)); } @ApiOperation(value = "Download all build artifact as tar or zip archive", notes = "Download all build artifacts as tar or zip archive", position = 8) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GET @Path("/download-all/{id}") public void downloadResultArchive(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Build ID", required = true) @PathParam("id") Long id, @ApiParam(value = "Archive type", defaultValue = "tar", allowableValues = "tar,zip") @Required @QueryParam("arch") String arch, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream buildQueue.getTask(id).downloadResultArchive(arch, new HttpServletProxyResponse(httpServletResponse)); } @ApiOperation(value = "Get all builders", notes = "Get information on all registered builders", response = BuilderDescriptor.class, responseContainer = "List", position = 9) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Server error")}) @GenerateLink(rel = Constants.LINK_REL_AVAILABLE_BUILDERS) @GET @Produces(MediaType.APPLICATION_JSON) @Path("/builders") public List<BuilderDescriptor> getRegisteredServers(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace) { final List<RemoteBuilderServer> builderServers = buildQueue.getRegisterBuilderServers(); for (Iterator<RemoteBuilderServer> itr = builderServers.iterator(); itr.hasNext(); ) { final RemoteBuilderServer builderServer = itr.next(); if (!builderServer.isAvailable()) { LOG.error("Builder server {} becomes unavailable", builderServer.getBaseUrl()); itr.remove(); } } final List<BuilderDescriptor> result = new LinkedList<>(); for (RemoteBuilderServer builderServer : builderServers) { final String assignedWorkspace = builderServer.getAssignedWorkspace(); if (assignedWorkspace == null || assignedWorkspace.equals(workspace)) { try { result.addAll(builderServer.getBuilderDescriptors()); } catch (BuilderException e) { LOG.error(e.getMessage(), e); } } } return result; } }