Add limited support for Makefile parsing. (#103)
* Add limited support for Makefile parsing. As the AWS Linux images do not contain make. * javaExec: Extract makefile functionality in its own class * Implement review comments
This commit is contained in:
@ -1,15 +1,6 @@
|
||||
package poseidon;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import com.amazonaws.client.builder.AwsClientBuilder;
|
||||
import com.amazonaws.regions.Regions;
|
||||
import com.amazonaws.services.apigatewaymanagementapi.AmazonApiGatewayManagementApi;
|
||||
import com.amazonaws.services.apigatewaymanagementapi.AmazonApiGatewayManagementApiClientBuilder;
|
||||
import com.amazonaws.services.apigatewaymanagementapi.model.PostToConnectionRequest;
|
||||
@ -20,6 +11,16 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketEvent;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
// AwsFunctionRequest contains the java files that needs to be executed.
|
||||
class AwsFunctionRequest {
|
||||
String[] cmd;
|
||||
@ -64,6 +65,16 @@ public class App implements RequestHandler<APIGatewayV2WebSocketEvent, APIGatewa
|
||||
// disableOutput: If set to true, no output will be sent over the WebSocket connection.
|
||||
private boolean disableOutput = false;
|
||||
|
||||
// Unwrapps the passed command. We expect a "sh -c" wrapped command.
|
||||
public static String unwrapCommand(String[] cmd) {
|
||||
return cmd[cmd.length - 1];
|
||||
}
|
||||
|
||||
// Wrapps the passed command with "sh -c".
|
||||
public static String[] wrapCommand(String cmd) {
|
||||
return new String[]{"sh", "-c", cmd};
|
||||
}
|
||||
|
||||
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayV2WebSocketEvent input, final Context context) {
|
||||
APIGatewayV2WebSocketEvent.RequestContext ctx = input.getRequestContext();
|
||||
String[] domains = ctx.getDomainName().split("\\.");
|
||||
@ -78,7 +89,13 @@ public class App implements RequestHandler<APIGatewayV2WebSocketEvent, APIGatewa
|
||||
try {
|
||||
File workingDirectory = this.writeFS(execution.files);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(execution.cmd);
|
||||
String[] cmd = execution.cmd;
|
||||
try {
|
||||
SimpleMakefile make = new SimpleMakefile(execution.files);
|
||||
cmd = wrapCommand(make.parseCommand(unwrapCommand(execution.cmd)));
|
||||
} catch (NoMakefileFoundException | NoMakeCommandException | InvalidMakefileException ignored) {}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||
pb.directory(workingDirectory);
|
||||
Process p = pb.start();
|
||||
InputStream stdout = p.getInputStream(), stderr = p.getErrorStream();
|
||||
|
101
deploy/aws/java11Exec/src/main/java/poseidon/SimpleMakefile.java
Normal file
101
deploy/aws/java11Exec/src/main/java/poseidon/SimpleMakefile.java
Normal file
@ -0,0 +1,101 @@
|
||||
package poseidon;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// NoMakefileFoundException is thrown if no makefile could be found.
|
||||
class NoMakefileFoundException extends Exception {}
|
||||
|
||||
// NoMakeCommandException is thrown if no make command is called.
|
||||
class NoMakeCommandException extends Exception {}
|
||||
|
||||
// InvalidMakefileException is thrown if there is no valid rule to be executed.
|
||||
class InvalidMakefileException extends Exception {}
|
||||
|
||||
// SimpleMakefile adds limited support for the execution of a makefile as passed command. The default Java image does not contain make.
|
||||
class SimpleMakefile {
|
||||
|
||||
// This pattern validates if a command is a make command.
|
||||
private static final Pattern isMakeCommand = Pattern.compile("^make(?:\\s+(?<startRule>\\w*))?$");
|
||||
|
||||
// This pattern identifies the rules in a makefile.
|
||||
private static final Pattern makeRules = Pattern.compile("(?<name>.*):\\n(?<commands>(?:\\t.+\\n?)*)");
|
||||
|
||||
// The first rule of the makefile.
|
||||
private String firstRule = null;
|
||||
|
||||
// The rules included in the makefile.
|
||||
private final Map<String, String[]> rules = new HashMap<>();
|
||||
|
||||
|
||||
private static String concatCommands(String[] commands) {
|
||||
return String.join(" && ", commands);
|
||||
}
|
||||
|
||||
// getMakefile returns the makefile out of the passed files map.
|
||||
private static String getMakefile(Map<String, String> files) throws NoMakefileFoundException {
|
||||
String makefileB64;
|
||||
if (files.containsKey("Makefile")) {
|
||||
makefileB64 = files.get("Makefile");
|
||||
} else if (files.containsKey("makefile")) {
|
||||
makefileB64 = files.get("makefile");
|
||||
} else {
|
||||
throw new NoMakefileFoundException();
|
||||
}
|
||||
|
||||
return new String(Base64.getDecoder().decode(makefileB64), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public SimpleMakefile(Map<String, String> files) throws NoMakefileFoundException {
|
||||
this.parseRules(getMakefile(files));
|
||||
}
|
||||
|
||||
// parseRules uses the passed makefile to parse rules into the objet's map "rules".
|
||||
private void parseRules(String makefile) {
|
||||
Matcher makeRuleMatcher = makeRules.matcher(makefile);
|
||||
while (makeRuleMatcher.find()) {
|
||||
String ruleName = makeRuleMatcher.group("name");
|
||||
if (firstRule == null) {
|
||||
firstRule = ruleName;
|
||||
}
|
||||
|
||||
String[] ruleCommands = makeRuleMatcher.group("commands").split("\n");
|
||||
String[] trimmedCommands = Arrays.stream(ruleCommands).map(String::trim).toArray(String[]::new);
|
||||
|
||||
rules.put(ruleName, trimmedCommands);
|
||||
}
|
||||
}
|
||||
|
||||
// getCommand returns a bash line of commands that would be executed by the passed rule.
|
||||
public String getCommand(String rule) {
|
||||
if (rule == null || rule.isEmpty()) {
|
||||
rule = this.firstRule;
|
||||
}
|
||||
|
||||
return concatCommands(rules.get(rule));
|
||||
}
|
||||
|
||||
// parseCommand returns a bash line of commands that would be executed by the passed command.
|
||||
public String parseCommand(String shellCommand) throws InvalidMakefileException, NoMakeCommandException {
|
||||
Matcher makeCommandMatcher = isMakeCommand.matcher(shellCommand);
|
||||
if (!makeCommandMatcher.find()) {
|
||||
throw new NoMakeCommandException();
|
||||
}
|
||||
|
||||
String ruleArgument = makeCommandMatcher.group("startRule");
|
||||
if (ruleArgument.isEmpty()) {
|
||||
ruleArgument = this.firstRule;
|
||||
}
|
||||
|
||||
if ((this.firstRule == null) || !rules.containsKey(ruleArgument)) {
|
||||
throw new InvalidMakefileException();
|
||||
}
|
||||
|
||||
return getCommand(ruleArgument);
|
||||
}
|
||||
}
|
@ -4,12 +4,30 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
|
||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketEvent;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
||||
public class AppTest {
|
||||
|
||||
static final String RecursiveMathContent = Base64.getEncoder().encodeToString(
|
||||
("package org.example;\n" +
|
||||
"\n" +
|
||||
"public class RecursiveMath {\n" +
|
||||
"\n" +
|
||||
" public static void main(String[] args) {\n" +
|
||||
" System.out.println(\"Mein Text\");\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" public static double power(int base, int exponent) {\n" +
|
||||
" return 42;\n" +
|
||||
" }\n" +
|
||||
"}").getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@Test
|
||||
public void successfulResponse() {
|
||||
App app = new App();
|
||||
@ -21,7 +39,8 @@ public class AppTest {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put(App.disableOutputHeaderKey, "True");
|
||||
input.setHeaders(headers);
|
||||
input.setBody("{\n \"action\": \"java11Exec\",\n \"cmd\": [\n \"sh\",\n \"-c\",\n \"javac org/example/RecursiveMath.java && java org/example/RecursiveMath\"\n ],\n \"files\": {\n \"org/example/RecursiveMath.java\": \"cGFja2FnZSBvcmcuZXhhbXBsZTsKCnB1YmxpYyBjbGFzcyBSZWN1cnNpdmVNYXRoIHsKCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgbWFpbihTdHJpbmdbXSBhcmdzKSB7CiAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJNZWluIFRleHQiKTsKICAgIH0KCiAgICBwdWJsaWMgc3RhdGljIGRvdWJsZSBwb3dlcihpbnQgYmFzZSwgaW50IGV4cG9uZW50KSB7CiAgICAgICAgcmV0dXJuIDQyOwogICAgfQp9Cgo=\"\n }\n}");
|
||||
input.setBody("{\"action\":\"java11Exec\",\"cmd\":[\"sh\",\"-c\",\"javac org/example/RecursiveMath.java && java org/example/RecursiveMath\"]," +
|
||||
"\"files\":{\"org/example/RecursiveMath.java\":\"" + RecursiveMathContent + "\"}}");
|
||||
APIGatewayProxyResponseEvent result = app.handleRequest(input, null);
|
||||
assertEquals(200, result.getStatusCode().intValue());
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
package poseidon;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static poseidon.AppTest.RecursiveMathContent;
|
||||
|
||||
|
||||
public class SimpleMakefileTest {
|
||||
static final String SuccessfulMakefile = Base64.getEncoder().encodeToString(
|
||||
("run:\n" +
|
||||
"\tjavac org/example/RecursiveMath.java\n" +
|
||||
"\tjava org/example/RecursiveMath\n" +
|
||||
"\n" +
|
||||
"test:\n" +
|
||||
"\techo Hi\n"
|
||||
).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
static final String NotSupportedMakefile = Base64.getEncoder().encodeToString(
|
||||
("run: test\n" +
|
||||
"\tjavac org/example/RecursiveMath.java\n" +
|
||||
"\tjava org/example/RecursiveMath\n" +
|
||||
"\n" +
|
||||
"test:\n" +
|
||||
"\techo Hi\n"
|
||||
).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@Test
|
||||
public void sucessfullMake() {
|
||||
Map<String, String> files = new HashMap<>();
|
||||
files.put("Makefile", SuccessfulMakefile);
|
||||
files.put("org/example/RecursiveMath.java", RecursiveMathContent);
|
||||
|
||||
try {
|
||||
String command = "make run";
|
||||
SimpleMakefile makefile = new SimpleMakefile(files);
|
||||
String cmd = makefile.parseCommand(command);
|
||||
|
||||
assertEquals("javac org/example/RecursiveMath.java && java org/example/RecursiveMath", cmd);
|
||||
} catch (NoMakefileFoundException | InvalidMakefileException | NoMakeCommandException ignored) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutMake() {
|
||||
Map<String, String> files = new HashMap<>();
|
||||
files.put("Makefile", SuccessfulMakefile);
|
||||
files.put("org/example/RecursiveMath.java", RecursiveMathContent);
|
||||
|
||||
try {
|
||||
String command = "javac org/example/RecursiveMath.java";
|
||||
SimpleMakefile make = new SimpleMakefile(files);
|
||||
make.parseCommand(command);
|
||||
fail();
|
||||
} catch (NoMakefileFoundException | InvalidMakefileException ignored) {
|
||||
fail();
|
||||
} catch (NoMakeCommandException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withNotSupportedMakefile() {
|
||||
Map<String, String> files = new HashMap<>();
|
||||
files.put("Makefile", NotSupportedMakefile);
|
||||
files.put("org/example/RecursiveMath.java", RecursiveMathContent);
|
||||
|
||||
try {
|
||||
String command = "make run";
|
||||
SimpleMakefile makefile = new SimpleMakefile(files);
|
||||
makefile.parseCommand(command);
|
||||
fail();
|
||||
} catch (NoMakefileFoundException | NoMakeCommandException ignored) {
|
||||
fail();
|
||||
} catch (InvalidMakefileException ignored) {}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user