From d7b1c2d6912c59e9c2acbaae3b0c510b5f07c7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Pa=C3=9F?= <22845248+mpass99@users.noreply.github.com> Date: Thu, 7 Apr 2022 22:40:19 +0200 Subject: [PATCH] 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 --- .../src/main/java/poseidon/App.java | 37 +++++-- .../main/java/poseidon/SimpleMakefile.java | 101 ++++++++++++++++++ .../src/test/java/poseidon/AppTest.java | 21 +++- .../java/poseidon/SimpleMakefileTest.java | 82 ++++++++++++++ 4 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 deploy/aws/java11Exec/src/main/java/poseidon/SimpleMakefile.java create mode 100644 deploy/aws/java11Exec/src/test/java/poseidon/SimpleMakefileTest.java diff --git a/deploy/aws/java11Exec/src/main/java/poseidon/App.java b/deploy/aws/java11Exec/src/main/java/poseidon/App.java index 71f4a64..fa3237f 100644 --- a/deploy/aws/java11Exec/src/main/java/poseidon/App.java +++ b/deploy/aws/java11Exec/src/main/java/poseidon/App.java @@ -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\\w*))?$"); + + // This pattern identifies the rules in a makefile. + private static final Pattern makeRules = Pattern.compile("(?.*):\\n(?(?:\\t.+\\n?)*)"); + + // The first rule of the makefile. + private String firstRule = null; + + // The rules included in the makefile. + private final Map 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 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 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); + } +} diff --git a/deploy/aws/java11Exec/src/test/java/poseidon/AppTest.java b/deploy/aws/java11Exec/src/test/java/poseidon/AppTest.java index 1e41e98..42a0de1 100644 --- a/deploy/aws/java11Exec/src/test/java/poseidon/AppTest.java +++ b/deploy/aws/java11Exec/src/test/java/poseidon/AppTest.java @@ -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 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()); } diff --git a/deploy/aws/java11Exec/src/test/java/poseidon/SimpleMakefileTest.java b/deploy/aws/java11Exec/src/test/java/poseidon/SimpleMakefileTest.java new file mode 100644 index 0000000..ac183b9 --- /dev/null +++ b/deploy/aws/java11Exec/src/test/java/poseidon/SimpleMakefileTest.java @@ -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 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 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 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) {} + } +}