Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open sandbox files from SingularityUI #1854

Merged
merged 5 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions SingularityService/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,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 = /(?:\.([^.]+))?$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using something like FilenameUtils.getExtension(fileName); instead of a regex for clarity?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

java(script) :lol:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, javascript 🤦

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