Skip to content

Commit

Permalink
Merge pull request #1854 from HubSpot/open_sandbox_files
Browse files Browse the repository at this point in the history
Open sandbox files from SingularityUI
  • Loading branch information
ssalinas authored Sep 27, 2018
2 parents df0ddfd + 0240e41 commit 2b7a926
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 11 deletions.
5 changes: 5 additions & 0 deletions SingularityService/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,11 @@
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</dependency>

<!-- for jade -->
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
Expand All @@ -26,6 +27,7 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;

Expand Down Expand Up @@ -121,6 +123,7 @@ public class TaskResource extends AbstractLeaderAwareResource {
private final SingularityValidator validator;
private final DisasterManager disasterManager;
private final RequestHelper requestHelper;
private final MimetypesFileTypeMap fileTypeMap;

@Inject
public TaskResource(TaskRequestManager taskRequestManager, TaskManager taskManager, SlaveManager slaveManager, MesosClient mesosClient, SingularityTaskMetadataConfiguration taskMetadataConfiguration,
Expand All @@ -139,6 +142,7 @@ public TaskResource(TaskRequestManager taskRequestManager, TaskManager taskManag
this.requestHelper = requestHelper;
this.httpClient = httpClient;
this.configuration = configuration;
this.fileTypeMap = new MimetypesFileTypeMap();
}

@GET
Expand Down Expand Up @@ -636,11 +640,26 @@ public Response downloadFileOverProxy(
@Parameter(required = true, description = "Mesos slave hostname") @QueryParam("slaveHostname") String slaveHostname,
@Parameter(required = true, description = "Full file path to file on Mesos slave to be downloaded") @QueryParam("path") String fileFullPath
) {
return getFile(slaveHostname, fileFullPath, true);
}

@GET
@Path("/open/")
@Produces("*/*")
@Operation(summary = "Open a file from a Mesos Slave through Singularity")
public Response openFileOverProxy(
@Parameter(required = true, description = "Mesos slave hostname") @QueryParam("slaveHostname") String slaveHostname,
@Parameter(required = true, description = "Full file path to file on Mesos slave to be downloaded") @QueryParam("path") String fileFullPath
) {
return getFile(slaveHostname, fileFullPath, false);
}

private Response getFile(String slaveHostname, String fileFullPath, boolean download) {
String httpPrefix = configuration.getSlaveHttpsPort().isPresent() ? "https" : "http";
int httpPort = configuration.getSlaveHttpsPort().isPresent() ? configuration.getSlaveHttpsPort().get() : configuration.getSlaveHttpPort();

String url = String.format("%s://%s:%s/files/download.json",
httpPrefix, slaveHostname, httpPort);
httpPrefix, slaveHostname, httpPort);

try {
PerRequestConfig unlimitedTimeout = new PerRequestConfig();
Expand All @@ -657,16 +676,24 @@ public Response downloadFileOverProxy(
java.nio.file.Path filePath = Paths.get(fileFullPath).getFileName();
String fileName = filePath != null ? filePath.toString() : fileFullPath;

final String headerValue = String.format("attachment; filename=\"%s\"", fileName);
return Response.ok(streamingOutputNingHandler).header("Content-Disposition", headerValue).build();
ResponseBuilder responseBuilder = Response.ok(streamingOutputNingHandler);

if (download) {
final String headerValue = String.format("attachment; filename=\"%s\"", fileName);
responseBuilder.header("Content-Disposition", headerValue);
} else {
// Guess type based on extension since we don't have the file locally to check content
final String maybeContentType = fileTypeMap.getContentType(fileFullPath);
responseBuilder.header("Content-Type", maybeContentType);
}
return responseBuilder.build();
} catch (Exception e) {
if (e.getCause().getClass() == ConnectException.class) {
throw new SlaveNotFoundException(e);
} else {
throw new RuntimeException(e);
}
}

}

private static class NingOutputToJaxRsStreamingOutputWrapper implements AsyncHandler<Void>, StreamingOutput {
Expand Down
4 changes: 4 additions & 0 deletions SingularityUI/app/components/taskDetail/TaskDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ class TaskDetail extends Component {

file.fullPath = `${files.fullPathToRoot}/${files.currentDirectory}/${file.name}`;
file.downloadLink = `${config.apiRoot}/tasks/download?slaveHostname=${files.slaveHostname}&path=${file.fullPath}`;
const extensionMatcher = /(?:\.([^.]+))?$/;
if (!file.isDirectory && Utils.OPENABLE_EXTENSIONS.includes(extensionMatcher.exec(file.name)[1])) {
file.openLink = `${config.apiRoot}/tasks/open?slaveHostname=${files.slaveHostname}&path=${file.fullPath}`;
}
file.isRecentlyModified = Date.now() / 1000 - file.mtime <= RECENTLY_MODIFIED_SECONDS;

if (!file.isDirectory) {
Expand Down
29 changes: 22 additions & 7 deletions SingularityUI/app/components/taskDetail/TaskFileBrowser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,28 @@ function TaskFileBrowser (props) {
id="actions-column"
key="actions-column"
className="actions-column"
cellData={(file) => !file.isDirectory && (
<OverlayTrigger placement="left" overlay={<ToolTip id={`downloadFile${file.name}`}>Download {file.name}</ToolTip>}>
<a href={file.downloadLink}>
<Glyphicon glyph="download-alt" />
</a>
</OverlayTrigger>
)}
cellData={(file) => {
const download = !file.isDirectory && (
<OverlayTrigger placement="left" overlay={<ToolTip id={`downloadFile${file.name}`}>Download {file.name}</ToolTip>}>
<a href={file.downloadLink}>
<Glyphicon glyph="download-alt" />
</a>
</OverlayTrigger>
);
const open = !file.isDirectory && file.openLink && (
<OverlayTrigger placement="left" overlay={<ToolTip id={`openFile${file.name}`}>Open {file.name}</ToolTip>}>
<a href={file.openLink}>
<Glyphicon glyph="open-file" />
</a>
</OverlayTrigger>
)
return (
<div>
{open}
{download}
</div>
);
}}
/>
</UITable>
</div>
Expand Down
2 changes: 2 additions & 0 deletions SingularityUI/app/utils.es6
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const Utils = {

DEFAULT_SLAVES_COLUMNS: {'id': true, 'state': true, 'since': true, 'rack': true, 'host': true, 'uptime': true, 'actionUser': true, 'message': true, 'expiring': true},

OPENABLE_EXTENSIONS: ['svg', 'txt', 'jpg', 'jpeg', 'gif', 'png', 'pdf', 'html'],

isIn(needle, haystack) {
return !_.isEmpty(haystack) && haystack.indexOf(needle) >= 0;
},
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@
<version>1.18</version>
</dependency>

<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1</version>
</dependency>

<dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
Expand Down

0 comments on commit 2b7a926

Please sign in to comment.