From a0303817b8d593599a1d39e1805093f5e2b2f274 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Fri, 1 Sep 2017 19:15:33 +0200 Subject: [PATCH 01/18] Invoke local java functionality --- .gitignore | 1 + lib/plugins/aws/invokeLocal/Invoke.java | 68 +++++++++++++++++++++++++ lib/plugins/aws/invokeLocal/index.js | 42 +++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 lib/plugins/aws/invokeLocal/Invoke.java diff --git a/.gitignore b/.gitignore index b49e4e927..5a7613c76 100755 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ tracking-config.json # Misc jest +lib/plugins/aws/invokeLocal/Invoke.class diff --git a/lib/plugins/aws/invokeLocal/Invoke.java b/lib/plugins/aws/invokeLocal/Invoke.java new file mode 100644 index 000000000..fd54d3720 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/Invoke.java @@ -0,0 +1,68 @@ +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.Class; +import java.lang.reflect.Type; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.MalformedURLException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.io.BufferedReader; + +public class Invoke { + private File artifact; + private String className; + private Object instance; + private Class clazz; + + public Invoke() { + this.artifact = new File(new File("."), System.getProperty("artifactPath")); + this.className = System.getProperty("className"); + + try { + HashMap parsedInput = new HashMap<>(); + String input = getInput(); + + // parsedInput = this.parseInput(input); - should parse String -> Json -> Map + // Context - no ideas... + + this.instance = this.getInstance(); + this.invoke(new HashMap(), null); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Object getInstance() throws Exception { + URL[] urls = {this.artifact.toURI().toURL()}; + URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader()); + + this.clazz = Class.forName(this.className, true, child); + + return this.clazz.newInstance(); + } + + private Object invoke(HashMap event, Object context) throws Exception { + Method[] methods = this.clazz.getDeclaredMethods(); + + return methods[1].invoke(this.instance, event, context); + } + + private String getInput() throws IOException { + BufferedReader streamReader = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + StringBuilder inputStringBuilder = new StringBuilder(); + String inputStr; + + while ((inputStr = streamReader.readLine()) != null) { + inputStringBuilder.append(inputStr); + } + + return inputStringBuilder.toString(); + } + + public static void main(String[] args) { + new Invoke(); + } +} diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 927c212a1..09c088751 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -136,6 +136,15 @@ class AwsInvokeLocal { this.options.context); } + if (runtime === 'java8') { + return this.invokeLocalJava( + 'java', + handler, + this.serverless.service.package.artifact, + this.options.data, + this.options.context); + } + throw new this.serverless.classes .Error('You can only invoke Node.js & Python functions locally.'); } @@ -161,6 +170,39 @@ class AwsInvokeLocal { }); } + invokeLocalJava(runtime, className, artifactPath, event, context) { + const input = JSON.stringify({ + event: event || {}, + context, + }); + + if (process.env.VIRTUAL_ENV) { + process.env.PATH = `${process.env.VIRTUAL_ENV}/bin:${process.env.PATH}`; + } + + return new BbPromise(resolve => { + const javac = spawn('javac', [path.join(__dirname, 'Invoke.java')]); + + javac.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`javac - ${buf.toString()}`)); + javac.stdin.end(); + javac.on('close', () => { + const java = spawn('java', [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + '-cp', + __dirname, + 'Invoke', + ]); + + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stdin.write(input); + java.stdin.end(); + java.on('close', () => resolve()); + }); + }); + } + invokeLocalNodeJs(handlerPath, handlerName, event, customContext) { let lambda; From faf696b77da0846657db569d4096bf255c533202 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Sat, 2 Sep 2017 15:45:31 +0200 Subject: [PATCH 02/18] Add maven as dependency manager --- .gitignore | 2 +- lib/plugins/aws/invokeLocal/index.js | 62 ++++++++++------- lib/plugins/aws/invokeLocal/java/MANIFEST.mf | 2 + lib/plugins/aws/invokeLocal/java/pom.xml | 66 +++++++++++++++++++ .../src/main/java/com/serverless/Context.java | 51 ++++++++++++++ .../src/main/java/com/serverless}/Invoke.java | 36 ++++++---- 6 files changed, 181 insertions(+), 38 deletions(-) create mode 100644 lib/plugins/aws/invokeLocal/java/MANIFEST.mf create mode 100644 lib/plugins/aws/invokeLocal/java/pom.xml create mode 100644 lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java rename lib/plugins/aws/invokeLocal/{ => java/src/main/java/com/serverless}/Invoke.java (64%) diff --git a/.gitignore b/.gitignore index 5a7613c76..52fb14cbd 100755 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ tracking-config.json # Misc jest -lib/plugins/aws/invokeLocal/Invoke.class +lib/plugins/aws/invokeLocal/java/target diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 09c088751..10212d979 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -141,8 +141,7 @@ class AwsInvokeLocal { 'java', handler, this.serverless.service.package.artifact, - this.options.data, - this.options.context); + this.options.data); } throw new this.serverless.classes @@ -170,36 +169,53 @@ class AwsInvokeLocal { }); } - invokeLocalJava(runtime, className, artifactPath, event, context) { - const input = JSON.stringify({ - event: event || {}, - context, + callJavaBridge(artifactPath, className, input) { + return new BbPromise((resolve) => { + const java = spawn('java', [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + '-jar', + path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), + ]); + + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stdin.write(input); + java.stdin.end(); + java.on('close', () => resolve()); }); + } + + invokeLocalJava(runtime, className, artifactPath, event) { + const input = JSON.stringify(event || {}); + const javaBridgePath = path.join(__dirname, 'java'); + const executablePath = path.join(javaBridgePath, 'target'); if (process.env.VIRTUAL_ENV) { process.env.PATH = `${process.env.VIRTUAL_ENV}/bin:${process.env.PATH}`; } return new BbPromise(resolve => { - const javac = spawn('javac', [path.join(__dirname, 'Invoke.java')]); - - javac.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`javac - ${buf.toString()}`)); - javac.stdin.end(); - javac.on('close', () => { - const java = spawn('java', [ - `-DartifactPath=${artifactPath}`, - `-DclassName=${className}`, - '-cp', - __dirname, - 'Invoke', + if (!this.serverless.utils.dirExistsSync(executablePath)) { + const mvn = spawn('mvn', [ + 'package', + '-f', + javaBridgePath, ]); - java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stdin.write(input); - java.stdin.end(); - java.on('close', () => resolve()); - }); + this.serverless.cli.consoleLog( + 'Building Java bridge, first invocation might take a bit longer.' + ); + + mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); + mvn.stdin.end(); + + mvn.on('close', () => { + this.callJavaBridge(artifactPath, className, input).then(resolve); + }); + } else { + this.callJavaBridge(artifactPath, className, input).then(resolve); + } }); } diff --git a/lib/plugins/aws/invokeLocal/java/MANIFEST.mf b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf new file mode 100644 index 000000000..13a041fef --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf @@ -0,0 +1,2 @@ +Manifest-version: 1.0 +Main-Class: com.serverless.Invoke diff --git a/lib/plugins/aws/invokeLocal/java/pom.xml b/lib/plugins/aws/invokeLocal/java/pom.xml new file mode 100644 index 000000000..5c225a212 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + com.serverless + invoke-bridge + 1.0 + + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + + + com.amazonaws + aws-lambda-java-log4j + 1.0.0 + + + com.fasterxml.jackson.core + jackson-core + 2.8.5 + + + com.fasterxml.jackson.core + jackson-databind + 2.8.5 + + + com.fasterxml.jackson.core + jackson-annotations + 2.8.5 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + com.serverless.Invoke + + + + + + + + + diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java new file mode 100644 index 000000000..13fc5cec0 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java @@ -0,0 +1,51 @@ +package com.serverless; + +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.LambdaLogger; + +public class Context implements com.amazonaws.services.lambda.runtime.Context { + public String getAwsRequestId() { + return null; + } + + public String getLogGroupName() { + return null; + } + + public String getLogStreamName() { + return null; + } + + public String getFunctionName() { + return null; + } + + public String getFunctionVersion() { + return null; + } + + public String getInvokedFunctionArn() { + return null; + } + + public CognitoIdentity getIdentity() { + return null; + } + + public ClientContext getClientContext() { + return null; + } + + public int getRemainingTimeInMillis() { + return 0; + } + + public int getMemoryLimitInMB() { + return 0; + } + + public LambdaLogger getLogger() { + return null; + } +} diff --git a/lib/plugins/aws/invokeLocal/Invoke.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java similarity index 64% rename from lib/plugins/aws/invokeLocal/Invoke.java rename to lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java index fd54d3720..b6bc348b2 100644 --- a/lib/plugins/aws/invokeLocal/Invoke.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java @@ -1,15 +1,17 @@ +package com.serverless; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import java.lang.Class; -import java.lang.reflect.Type; +import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; -import java.net.MalformedURLException; -import java.lang.reflect.Method; import java.util.HashMap; -import java.util.Map; -import java.io.BufferedReader; public class Invoke { private File artifact; @@ -17,19 +19,16 @@ public class Invoke { private Object instance; private Class clazz; - public Invoke() { + private Invoke() { this.artifact = new File(new File("."), System.getProperty("artifactPath")); this.className = System.getProperty("className"); try { - HashMap parsedInput = new HashMap<>(); - String input = getInput(); - - // parsedInput = this.parseInput(input); - should parse String -> Json -> Map - // Context - no ideas... + HashMap parsedInput = parseInput(getInput()); + System.out.println(getInput()); this.instance = this.getInstance(); - this.invoke(new HashMap(), null); + System.out.println(this.invoke(parsedInput, new Context())); } catch (Exception e) { e.printStackTrace(); } @@ -44,12 +43,21 @@ public class Invoke { return this.clazz.newInstance(); } - private Object invoke(HashMap event, Object context) throws Exception { + private Object invoke(HashMap event, Context context) throws Exception { Method[] methods = this.clazz.getDeclaredMethods(); return methods[1].invoke(this.instance, event, context); } + private HashMap parseInput(String input) throws IOException { + TypeReference> typeRef = new TypeReference>() {}; + ObjectMapper mapper = new ObjectMapper(); + + JsonNode jsonNode = mapper.readTree(input); + + return mapper.convertValue(jsonNode, typeRef); + } + private String getInput() throws IOException { BufferedReader streamReader = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); StringBuilder inputStringBuilder = new StringBuilder(); From c9bb961fd00a7d2608d85779c106ef326c04755b Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Sat, 2 Sep 2017 15:46:32 +0200 Subject: [PATCH 03/18] Remove unnecessary code --- .../invokeLocal/java/src/main/java/com/serverless/Invoke.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java index b6bc348b2..ab381abdf 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java @@ -25,7 +25,6 @@ public class Invoke { try { HashMap parsedInput = parseInput(getInput()); - System.out.println(getInput()); this.instance = this.getInstance(); System.out.println(this.invoke(parsedInput, new Context())); From 3cf32ef60ac48f10e68f591ba348f2edf568e17e Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Sat, 2 Sep 2017 22:02:16 +0200 Subject: [PATCH 04/18] Add custom context passing --- lib/plugins/aws/invokeLocal/index.js | 10 +++- lib/plugins/aws/invokeLocal/java/MANIFEST.mf | 2 +- lib/plugins/aws/invokeLocal/java/pom.xml | 2 +- .../src/main/java/com/serverless/Context.java | 55 +++++++++++++++---- .../{Invoke.java => InvokeBridge.java} | 20 +++++-- 5 files changed, 69 insertions(+), 20 deletions(-) rename lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/{Invoke.java => InvokeBridge.java} (74%) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 10212d979..d961c34cd 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -141,7 +141,8 @@ class AwsInvokeLocal { 'java', handler, this.serverless.service.package.artifact, - this.options.data); + this.options.data, + this.options.context); } throw new this.serverless.classes @@ -186,10 +187,13 @@ class AwsInvokeLocal { }); } - invokeLocalJava(runtime, className, artifactPath, event) { - const input = JSON.stringify(event || {}); + invokeLocalJava(runtime, className, artifactPath, event, context) { const javaBridgePath = path.join(__dirname, 'java'); const executablePath = path.join(javaBridgePath, 'target'); + const input = JSON.stringify({ + event: event || {}, + context: context || {}, + }); if (process.env.VIRTUAL_ENV) { process.env.PATH = `${process.env.VIRTUAL_ENV}/bin:${process.env.PATH}`; diff --git a/lib/plugins/aws/invokeLocal/java/MANIFEST.mf b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf index 13a041fef..3cc01de12 100644 --- a/lib/plugins/aws/invokeLocal/java/MANIFEST.mf +++ b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf @@ -1,2 +1,2 @@ Manifest-version: 1.0 -Main-Class: com.serverless.Invoke +Main-Class: com.serverless.InvokeBridge diff --git a/lib/plugins/aws/invokeLocal/java/pom.xml b/lib/plugins/aws/invokeLocal/java/pom.xml index 5c225a212..80aa516a9 100644 --- a/lib/plugins/aws/invokeLocal/java/pom.xml +++ b/lib/plugins/aws/invokeLocal/java/pom.xml @@ -54,7 +54,7 @@ - com.serverless.Invoke + com.serverless.InvokeBridge diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java index 13fc5cec0..035e23cbe 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java @@ -1,51 +1,84 @@ package com.serverless; +import com.amazonaws.services.lambda.runtime.Client; import com.amazonaws.services.lambda.runtime.ClientContext; import com.amazonaws.services.lambda.runtime.CognitoIdentity; import com.amazonaws.services.lambda.runtime.LambdaLogger; +import java.util.Map; + public class Context implements com.amazonaws.services.lambda.runtime.Context { + private String name; + private String version; + private long endTime; + + Context(String name, String version, int timeout) { + this.name = name; + this.version = version; + this.endTime = System.currentTimeMillis() + (timeout * 1000); + } + public String getAwsRequestId() { - return null; + return "1234567890"; } public String getLogGroupName() { - return null; + return "LogGroup_" + this.name; } public String getLogStreamName() { - return null; + return "LogStream_" + this.name; } public String getFunctionName() { - return null; + return this.name; } public String getFunctionVersion() { - return null; + return this.version; } public String getInvokedFunctionArn() { - return null; + return "arn:aws:lambda:serverless:" + this.name; } public CognitoIdentity getIdentity() { - return null; + return new CognitoIdentity() { + public String getIdentityId() { + return "1"; + } + + public String getIdentityPoolId() { + return "1"; + } + }; } public ClientContext getClientContext() { - return null; + return new ClientContext() { + public Client getClient() { + return null; + } + + public Map getCustom() { + return null; + } + + public Map getEnvironment() { + return System.getenv(); + } + }; } public int getRemainingTimeInMillis() { - return 0; + return Math.max(0, (int) (this.endTime - System.currentTimeMillis())); } public int getMemoryLimitInMB() { - return 0; + return 1024; } public LambdaLogger getLogger() { - return null; + return System.out::println; } } diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java similarity index 74% rename from lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java rename to lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java index ab381abdf..61e1c05d8 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Invoke.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java @@ -13,26 +13,38 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; -public class Invoke { +public class InvokeBridge { private File artifact; private String className; private Object instance; private Class clazz; - private Invoke() { + private InvokeBridge() { this.artifact = new File(new File("."), System.getProperty("artifactPath")); this.className = System.getProperty("className"); try { HashMap parsedInput = parseInput(getInput()); + HashMap eventMap = (HashMap) parsedInput.get("event"); this.instance = this.getInstance(); - System.out.println(this.invoke(parsedInput, new Context())); + + System.out.println(this.invoke(eventMap, this.getContext(parsedInput)).toString()); } catch (Exception e) { e.printStackTrace(); } } + private Context getContext(HashMap parsedInput) { + HashMap contextMap = (HashMap) parsedInput.get("context"); + + String name = (String) contextMap.getOrDefault("name", "name"); + String version = (String) contextMap.getOrDefault("version", "LATEST"); + int timeout = Integer.parseInt(String.valueOf(contextMap.getOrDefault("timeout", 5))); + + return new Context(name, version, timeout); + } + private Object getInstance() throws Exception { URL[] urls = {this.artifact.toURI().toURL()}; URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader()); @@ -70,6 +82,6 @@ public class Invoke { } public static void main(String[] args) { - new Invoke(); + new InvokeBridge(); } } From b1425c5ad5cbd0ab8b350edfed6b8324319780c6 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Sat, 2 Sep 2017 22:11:04 +0200 Subject: [PATCH 05/18] Context related fixes --- lib/plugins/aws/invokeLocal/index.js | 20 ++++++++++++------- .../src/main/java/com/serverless/Context.java | 6 ++++-- .../java/com/serverless/InvokeBridge.java | 5 +++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index d961c34cd..4107e2ced 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -187,17 +187,23 @@ class AwsInvokeLocal { }); } - invokeLocalJava(runtime, className, artifactPath, event, context) { - const javaBridgePath = path.join(__dirname, 'java'); - const executablePath = path.join(javaBridgePath, 'target'); + invokeLocalJava(runtime, className, artifactPath, event, customContext) { + const timeout = Number(this.options.functionObj.timeout) + || Number(this.serverless.service.provider.timeout) + || 6; + const context = { + name: this.options.functionObj.name, + version: 'LATEST', + logGroupName: this.provider.naming.getLogGroupName(this.options.functionObj.name), + timeout, + }; const input = JSON.stringify({ event: event || {}, - context: context || {}, + context: customContext || context, }); - if (process.env.VIRTUAL_ENV) { - process.env.PATH = `${process.env.VIRTUAL_ENV}/bin:${process.env.PATH}`; - } + const javaBridgePath = path.join(__dirname, 'java'); + const executablePath = path.join(javaBridgePath, 'target'); return new BbPromise(resolve => { if (!this.serverless.utils.dirExistsSync(executablePath)) { diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java index 035e23cbe..b0c93945c 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java @@ -10,11 +10,13 @@ import java.util.Map; public class Context implements com.amazonaws.services.lambda.runtime.Context { private String name; private String version; + private String logGroupName; private long endTime; - Context(String name, String version, int timeout) { + Context(String name, String version, String logGroupName, int timeout) { this.name = name; this.version = version; + this.logGroupName = logGroupName; this.endTime = System.currentTimeMillis() + (timeout * 1000); } @@ -23,7 +25,7 @@ public class Context implements com.amazonaws.services.lambda.runtime.Context { } public String getLogGroupName() { - return "LogGroup_" + this.name; + return this.logGroupName; } public String getLogStreamName() { diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java index 61e1c05d8..ab9629426 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java @@ -38,11 +38,12 @@ public class InvokeBridge { private Context getContext(HashMap parsedInput) { HashMap contextMap = (HashMap) parsedInput.get("context"); - String name = (String) contextMap.getOrDefault("name", "name"); + String name = (String) contextMap.getOrDefault("name", "functionName"); String version = (String) contextMap.getOrDefault("version", "LATEST"); + String logGroupName = (String) contextMap.getOrDefault("logGroupName", "logGroup"); int timeout = Integer.parseInt(String.valueOf(contextMap.getOrDefault("timeout", 5))); - return new Context(name, version, timeout); + return new Context(name, version, logGroupName, timeout); } private Object getInstance() throws Exception { From 1b03a43259b8aa269bf83e83abeca577bbc390ba Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Mon, 4 Sep 2017 12:08:48 +0200 Subject: [PATCH 06/18] Revert encoding change --- lib/plugins/package/lib/zipService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/package/lib/zipService.js b/lib/plugins/package/lib/zipService.js index fd0476fe0..a1354f16f 100644 --- a/lib/plugins/package/lib/zipService.js +++ b/lib/plugins/package/lib/zipService.js @@ -101,7 +101,7 @@ module.exports = { }, getFileContent(fullPath) { - return fs.readFileAsync(fullPath); + return fs.readFileAsync(fullPath, 'utf8'); }, }; From 612cec59c547248350a12f73b5a4d0b7dd8f7bba Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Mon, 4 Sep 2017 16:24:41 +0200 Subject: [PATCH 07/18] Fix unit test --- lib/plugins/aws/invokeLocal/index.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 48414c278..87e938e07 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -276,12 +276,15 @@ describe('AwsInvokeLocal', () => { describe('#invokeLocal()', () => { let invokeLocalNodeJsStub; let invokeLocalPythonStub; + let invokeLocalJavaStub; beforeEach(() => { invokeLocalNodeJsStub = sinon.stub(awsInvokeLocal, 'invokeLocalNodeJs').resolves(); invokeLocalPythonStub = sinon.stub(awsInvokeLocal, 'invokeLocalPython').resolves(); + invokeLocalJavaStub = + sinon.stub(awsInvokeLocal, 'invokeLocalJava').resolves(); awsInvokeLocal.serverless.service.service = 'new-service'; awsInvokeLocal.options = { @@ -298,6 +301,7 @@ describe('AwsInvokeLocal', () => { afterEach(() => { invokeLocalNodeJsStub.restore(); invokeLocalPythonStub.restore(); + invokeLocalJavaStub.restore(); }); it('should call invokeLocalNodeJs when no runtime is set', () => awsInvokeLocal.invokeLocal() @@ -355,11 +359,26 @@ describe('AwsInvokeLocal', () => { )).to.be.equal(true); awsInvokeLocal.invokeLocalPython.restore(); }); + }); + + it('should call invokeLocalJava when java8 runtime is set', () => { + awsInvokeLocal.options.functionObj.runtime = 'java8'; + awsInvokeLocal.invokeLocal() + .then(() => { + expect(invokeLocalJavaStub.calledOnce).to.be.equal(true); + expect(invokeLocalJavaStub.calledWithExactly( + 'java8', + 'handler', + 'hello', + {} + )).to.be.equal(true); + awsInvokeLocal.invokeLocalJava.restore(); + }); delete awsInvokeLocal.options.functionObj.runtime; }); it('throw error when using runtime other than Node.js or Python', () => { - awsInvokeLocal.options.functionObj.runtime = 'java8'; + awsInvokeLocal.options.functionObj.runtime = 'go'; expect(() => awsInvokeLocal.invokeLocal()).to.throw(Error); delete awsInvokeLocal.options.functionObj.runtime; }); From 0423c6e22867f50f933c72a2be3fe00418e23b93 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Wed, 6 Sep 2017 12:43:34 +0200 Subject: [PATCH 08/18] Improve tests coverage, add documentation --- .../aws/cli-reference/invoke-local.md | 4 +- lib/plugins/aws/invokeLocal/index.js | 2 +- lib/plugins/aws/invokeLocal/index.test.js | 209 +++++++++++++++++- 3 files changed, 202 insertions(+), 13 deletions(-) diff --git a/docs/providers/aws/cli-reference/invoke-local.md b/docs/providers/aws/cli-reference/invoke-local.md index e52daead2..20a618a98 100644 --- a/docs/providers/aws/cli-reference/invoke-local.md +++ b/docs/providers/aws/cli-reference/invoke-local.md @@ -96,7 +96,9 @@ This example will pass the json context in the `lib/context.json` file (relative ### Limitations -Currently, `invoke local` only supports the NodeJs and Python runtimes. +Currently, `invoke local` only supports the NodeJs, Python & Java runtimes. + +**Note:** In order to get correct output when using Java runtime, your Response class must implement `toString()` method. ## Resource permissions diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 4107e2ced..d1064a388 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -146,7 +146,7 @@ class AwsInvokeLocal { } throw new this.serverless.classes - .Error('You can only invoke Node.js & Python functions locally.'); + .Error('You can only invoke Node.js, Python & Java functions locally.'); } invokeLocalPython(runtime, handlerPath, handlerName, event, context) { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 2d2a0356f..796cfd630 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -3,6 +3,10 @@ const expect = require('chai').expect; const sinon = require('sinon'); const path = require('path'); +const BbPromise = require('bluebird'); +const mockRequire = require('mock-require'); +const EventEmitter = require('events'); +const fs = BbPromise.promisifyAll(require('graceful-fs')); const AwsInvokeLocal = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); @@ -299,9 +303,9 @@ describe('AwsInvokeLocal', () => { }); afterEach(() => { - awsInvokeLocal.invokeLocalNodeJsStub.restore(); - awsInvokeLocal.invokeLocalPythonStub.restore(); - awsInvokeLocal.invokeLocalJavaStub.restore(); + awsInvokeLocal.invokeLocalNodeJs.restore(); + awsInvokeLocal.invokeLocalPython.restore(); + awsInvokeLocal.invokeLocalJava.restore(); }); it('should call invokeLocalNodeJs when no runtime is set', () => awsInvokeLocal.invokeLocal() @@ -356,24 +360,22 @@ describe('AwsInvokeLocal', () => { {}, undefined )).to.be.equal(true); - delete awsInvokeLocal.options.functionObj.runtime; }); }); it('should call invokeLocalJava when java8 runtime is set', () => { awsInvokeLocal.options.functionObj.runtime = 'java8'; - awsInvokeLocal.invokeLocal() + return awsInvokeLocal.invokeLocal() .then(() => { expect(invokeLocalJavaStub.calledOnce).to.be.equal(true); expect(invokeLocalJavaStub.calledWithExactly( - 'java8', - 'handler', - 'hello', - {} + 'java', + 'handler.hello', + undefined, + {}, + undefined )).to.be.equal(true); - awsInvokeLocal.invokeLocalJava.restore(); }); - delete awsInvokeLocal.options.functionObj.runtime; }); it('throw error when using runtime other than Node.js or Python', () => { @@ -527,4 +529,189 @@ describe('AwsInvokeLocal', () => { }); }); }); + + describe('#callJavaBridge', () => { + let awsInvokeLocalMocked; + let writeChildStub; + let endChildStub; + + beforeEach(() => { + writeChildStub = sinon.stub(); + endChildStub = sinon.stub(); + + mockRequire('child_process', { + spawn: () => ({ + stderr: new EventEmitter().on('data', () => {}), + stdout: new EventEmitter().on('data', () => {}), + stdin: { + write: writeChildStub, + end: endChildStub, + }, + on: (key, callback) => callback(), + }), + }); + + // Remove Node.js internal "require cache" contents and re-require ./index.js + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + + const AwsInvokeLocalMocked = require('./index'); // eslint-disable-line global-require + + serverless.setProvider('aws', new AwsProvider(serverless)); + awsInvokeLocalMocked = new AwsInvokeLocalMocked(serverless, options); + + awsInvokeLocalMocked.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + }); + + afterEach(() => { + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + }); + + it('spawns java process with correct arguments', () => + awsInvokeLocalMocked.callJavaBridge( + __dirname, + 'com.serverless.Handler', + '{}' + ).then(() => { + expect(writeChildStub.calledOnce).to.be.equal(true); + expect(endChildStub.calledOnce).to.be.equal(true); + expect(writeChildStub.calledWithExactly('{}')).to.be.equal(true); + }) + ); + }); + + describe('#invokeLocalJava', () => { + let callJavaBridgeStub; + + beforeEach(() => { + callJavaBridgeStub = sinon.stub(awsInvokeLocal, 'callJavaBridge').resolves(); + awsInvokeLocal.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + + serverless.cli = new CLI(serverless); + sinon.stub(serverless.cli, 'consoleLog'); + }); + + afterEach(() => { + serverless.cli.consoleLog.restore(); + callJavaBridgeStub.restore(); + }); + + it('should invoke callJavaBridge when bridge is built', () => { + const bridgePath = path.join(__dirname, 'java', 'target'); + fs.mkdir(bridgePath); + + awsInvokeLocal.invokeLocalJava( + 'java', + 'com.serverless.Handler', + __dirname, + {} + ).then(() => { + expect(callJavaBridgeStub.calledOnce).to.be.equal(true); + expect(callJavaBridgeStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + )).to.be.equal(true); + fs.rmdirSync(bridgePath); + }); + }); + + describe('should build Java bridge', () => { + let awsInvokeLocalMocked; + let callJavaBridgeMockedStub; + + beforeEach(() => { + mockRequire('child_process', { + spawn: () => ({ + stderr: new EventEmitter().on('data', () => {}), + stdout: new EventEmitter().on('data', () => {}), + stdin: { + write: () => {}, + end: () => {}, + }, + on: (key, callback) => callback(), + }), + }); + + // Remove Node.js internal "require cache" contents and re-require ./index.js + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + + const AwsInvokeLocalMocked = require('./index'); // eslint-disable-line global-require + + serverless.setProvider('aws', new AwsProvider(serverless)); + awsInvokeLocalMocked = new AwsInvokeLocalMocked(serverless, options); + callJavaBridgeMockedStub = sinon.stub(awsInvokeLocalMocked, 'callJavaBridge').resolves(); + + awsInvokeLocalMocked.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + }); + + afterEach(() => { + callJavaBridgeMockedStub.restore(); + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + }); + + it('if it\'s not present yet', () => + awsInvokeLocalMocked.invokeLocalJava( + 'java', + 'com.serverless.Handler', + __dirname, + {} + ).then(() => { + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('Building Java bridge, first invocation might take a bit longer.'); //eslint-disable-line + expect(callJavaBridgeMockedStub.calledOnce).to.be.equal(true); + expect(callJavaBridgeMockedStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + )).to.be.equal(true); + }) + ); + }); + }); }); From 43be48ab3cc1caf2386950236b73d4990f2b605a Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Wed, 6 Sep 2017 18:27:39 +0200 Subject: [PATCH 09/18] Add missing maven source & target properties --- lib/plugins/aws/invokeLocal/java/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/java/pom.xml b/lib/plugins/aws/invokeLocal/java/pom.xml index 80aa516a9..c47be034b 100644 --- a/lib/plugins/aws/invokeLocal/java/pom.xml +++ b/lib/plugins/aws/invokeLocal/java/pom.xml @@ -8,6 +8,11 @@ invoke-bridge 1.0 + + 1.8 + 1.8 + + com.amazonaws From c304aab55d1e1fa8d81f286b6a813bf083219275 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Mon, 11 Sep 2017 18:34:12 +0200 Subject: [PATCH 10/18] Change javaBridgePath to include pom.xml --- lib/plugins/aws/invokeLocal/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index d1064a388..bf53e2113 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -210,7 +210,7 @@ class AwsInvokeLocal { const mvn = spawn('mvn', [ 'package', '-f', - javaBridgePath, + path.join(javaBridgePath, 'pom.xml'), ]); this.serverless.cli.consoleLog( From 2d09606cba226bf5e389fc55c570e122c094a6fc Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Mon, 11 Sep 2017 18:38:41 +0200 Subject: [PATCH 11/18] Move gitignore declaration --- .gitignore | 1 - lib/plugins/aws/invokeLocal/.gitignore | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 52fb14cbd..b49e4e927 100755 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,3 @@ tracking-config.json # Misc jest -lib/plugins/aws/invokeLocal/java/target diff --git a/lib/plugins/aws/invokeLocal/.gitignore b/lib/plugins/aws/invokeLocal/.gitignore index 0d20b6487..9b2db0a6c 100644 --- a/lib/plugins/aws/invokeLocal/.gitignore +++ b/lib/plugins/aws/invokeLocal/.gitignore @@ -1 +1,2 @@ *.pyc +java/target \ No newline at end of file From f3d9d0f53caac02e2dbb00594444208b584e0e08 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Tue, 12 Sep 2017 14:39:18 +0200 Subject: [PATCH 12/18] Add toString() notice --- lib/plugins/aws/invokeLocal/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index bf53e2113..b43650255 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -179,6 +179,11 @@ class AwsInvokeLocal { path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), ]); + this.serverless.cli.consoleLog([ + 'In order to get human-readable output,', + 'please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join(' ')); + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); java.stdin.write(input); From a989b4e7d28098f598cd62c4947f7ef6ffa3614e Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Tue, 12 Sep 2017 14:48:26 +0200 Subject: [PATCH 13/18] Get rid of log4j warnings --- lib/plugins/aws/invokeLocal/.gitignore | 5 ++++- .../aws-java-maven/src/main/java/com/serverless/Handler.java | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/.gitignore b/lib/plugins/aws/invokeLocal/.gitignore index 9b2db0a6c..376be9d3e 100644 --- a/lib/plugins/aws/invokeLocal/.gitignore +++ b/lib/plugins/aws/invokeLocal/.gitignore @@ -1,2 +1,5 @@ *.pyc -java/target \ No newline at end of file +java/target +java/.project +java/.settings +java/.classpath diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java index 80a6f78e8..6d6c670e5 100644 --- a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java +++ b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java @@ -3,6 +3,7 @@ package com.serverless; import java.util.Collections; import java.util.Map; +import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; import com.amazonaws.services.lambda.runtime.Context; @@ -14,6 +15,8 @@ public class Handler implements RequestHandler, ApiGatewayRe @Override public ApiGatewayResponse handleRequest(Map input, Context context) { + BasicConfigurator.configure(); + LOG.info("received: " + input); Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input); return ApiGatewayResponse.builder() From 105de7a60f1573b6cc0c9510b4d455b6b9cb10de Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Tue, 12 Sep 2017 15:01:16 +0200 Subject: [PATCH 14/18] Throw an error if artifact doesn't exist --- lib/plugins/aws/invokeLocal/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index b43650255..1c2261c70 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -2,6 +2,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); +const fs = require('fs'); const path = require('path'); const validate = require('../lib/validate'); const chalk = require('chalk'); @@ -172,6 +173,9 @@ class AwsInvokeLocal { callJavaBridge(artifactPath, className, input) { return new BbPromise((resolve) => { + // Throw an error if artifact doesn't exists + fs.statSync(artifactPath); + const java = spawn('java', [ `-DartifactPath=${artifactPath}`, `-DclassName=${className}`, From 5e8d67ce4bb7df0e787a5e276b563aeacaacc00c Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Wed, 13 Sep 2017 10:59:14 +0200 Subject: [PATCH 15/18] Code review fixes --- lib/plugins/aws/invokeLocal/index.js | 2 +- lib/plugins/aws/invokeLocal/index.test.js | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 1c2261c70..0c43230bd 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -183,7 +183,7 @@ class AwsInvokeLocal { path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), ]); - this.serverless.cli.consoleLog([ + this.serverless.cli.log([ 'In order to get human-readable output,', 'please implement "toString()" method of your "ApiGatewayResponse" object.', ].join(' ')); diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 796cfd630..79655d1e5 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -7,6 +7,7 @@ const BbPromise = require('bluebird'); const mockRequire = require('mock-require'); const EventEmitter = require('events'); const fs = BbPromise.promisifyAll(require('graceful-fs')); +const fsExtra = require('fs-extra'); const AwsInvokeLocal = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); @@ -379,7 +380,7 @@ describe('AwsInvokeLocal', () => { }); it('throw error when using runtime other than Node.js or Python', () => { - awsInvokeLocal.options.functionObj.runtime = 'go'; + awsInvokeLocal.options.functionObj.runtime = 'invalid-runtime'; expect(() => awsInvokeLocal.invokeLocal()).to.throw(Error); delete awsInvokeLocal.options.functionObj.runtime; }); @@ -591,6 +592,7 @@ describe('AwsInvokeLocal', () => { }); describe('#invokeLocalJava', () => { + const bridgePath = path.join(__dirname, 'java', 'target'); let callJavaBridgeStub; beforeEach(() => { @@ -612,12 +614,12 @@ describe('AwsInvokeLocal', () => { afterEach(() => { serverless.cli.consoleLog.restore(); - callJavaBridgeStub.restore(); + awsInvokeLocal.callJavaBridge.restore(); + fsExtra.removeSync(bridgePath); }); it('should invoke callJavaBridge when bridge is built', () => { - const bridgePath = path.join(__dirname, 'java', 'target'); - fs.mkdir(bridgePath); + fs.mkdirSync(bridgePath); awsInvokeLocal.invokeLocalJava( 'java', @@ -639,7 +641,6 @@ describe('AwsInvokeLocal', () => { }, }) )).to.be.equal(true); - fs.rmdirSync(bridgePath); }); }); @@ -683,7 +684,7 @@ describe('AwsInvokeLocal', () => { }); afterEach(() => { - callJavaBridgeMockedStub.restore(); + awsInvokeLocalMocked.callJavaBridge.restore(); delete require.cache[require.resolve('./index')]; delete require.cache[require.resolve('child_process')]; }); From 6b74a9c68bc9f710a407eef70485d94e919723d5 Mon Sep 17 00:00:00 2001 From: Rafal Wilinski Date: Wed, 13 Sep 2017 11:09:45 +0200 Subject: [PATCH 16/18] Change fs sync calls to async graceful --- lib/plugins/aws/invokeLocal/index.js | 49 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 0c43230bd..4996f6402 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -2,7 +2,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); -const fs = require('fs'); +const fs = BbPromise.promisifyAll(require('graceful-fs')); const path = require('path'); const validate = require('../lib/validate'); const chalk = require('chalk'); @@ -173,26 +173,27 @@ class AwsInvokeLocal { callJavaBridge(artifactPath, className, input) { return new BbPromise((resolve) => { - // Throw an error if artifact doesn't exists - fs.statSync(artifactPath); + fs.statAsync(artifactPath).then(() => { + const java = spawn('java', [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + '-jar', + path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), + ]); - const java = spawn('java', [ - `-DartifactPath=${artifactPath}`, - `-DclassName=${className}`, - '-jar', - path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), - ]); + this.serverless.cli.log([ + 'In order to get human-readable output,', + 'please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join(' ')); - this.serverless.cli.log([ - 'In order to get human-readable output,', - 'please implement "toString()" method of your "ApiGatewayResponse" object.', - ].join(' ')); - - java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stdin.write(input); - java.stdin.end(); - java.on('close', () => resolve()); + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stdin.write(input); + java.stdin.end(); + java.on('close', () => resolve()); + }).catch(() => { + throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); + }); }); } @@ -215,14 +216,16 @@ class AwsInvokeLocal { const executablePath = path.join(javaBridgePath, 'target'); return new BbPromise(resolve => { - if (!this.serverless.utils.dirExistsSync(executablePath)) { + fs.statAsync(executablePath).then(() => { + this.callJavaBridge(artifactPath, className, input).then(resolve); + }).catch(() => { const mvn = spawn('mvn', [ 'package', '-f', path.join(javaBridgePath, 'pom.xml'), ]); - this.serverless.cli.consoleLog( + this.serverless.cli.log( 'Building Java bridge, first invocation might take a bit longer.' ); @@ -232,9 +235,7 @@ class AwsInvokeLocal { mvn.on('close', () => { this.callJavaBridge(artifactPath, className, input).then(resolve); }); - } else { - this.callJavaBridge(artifactPath, className, input).then(resolve); - } + }); }); } From ff03b89c2f3481381c875911b0bc26a3b3972856 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 18 Sep 2017 11:06:14 +0200 Subject: [PATCH 17/18] Minor fixes to avoid race conditions --- lib/plugins/aws/invokeLocal/index.js | 53 +++++++++++------------ lib/plugins/aws/invokeLocal/index.test.js | 26 ++++------- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 4996f6402..37887cd36 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -2,7 +2,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); -const fs = BbPromise.promisifyAll(require('graceful-fs')); +const fs = BbPromise.promisifyAll(require('fs')); const path = require('path'); const validate = require('../lib/validate'); const chalk = require('chalk'); @@ -172,29 +172,27 @@ class AwsInvokeLocal { } callJavaBridge(artifactPath, className, input) { - return new BbPromise((resolve) => { - fs.statAsync(artifactPath).then(() => { - const java = spawn('java', [ - `-DartifactPath=${artifactPath}`, - `-DclassName=${className}`, - '-jar', - path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), - ]); + return new BbPromise((resolve) => fs.statAsync(artifactPath).then(() => { + const java = spawn('java', [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + '-jar', + path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), + ]); - this.serverless.cli.log([ - 'In order to get human-readable output,', - 'please implement "toString()" method of your "ApiGatewayResponse" object.', - ].join(' ')); + this.serverless.cli.log([ + 'In order to get human-readable output,', + 'please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join(' ')); - java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stdin.write(input); - java.stdin.end(); - java.on('close', () => resolve()); - }).catch(() => { - throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); - }); - }); + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stdin.write(input); + java.stdin.end(); + java.on('close', () => resolve()); + }).catch(() => { + throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); + })); } invokeLocalJava(runtime, className, artifactPath, event, customContext) { @@ -215,10 +213,10 @@ class AwsInvokeLocal { const javaBridgePath = path.join(__dirname, 'java'); const executablePath = path.join(javaBridgePath, 'target'); - return new BbPromise(resolve => { - fs.statAsync(executablePath).then(() => { - this.callJavaBridge(artifactPath, className, input).then(resolve); - }).catch(() => { + return new BbPromise(resolve => fs.statAsync(executablePath) + .then(() => this.callJavaBridge(artifactPath, className, input)) + .then(resolve) + .catch(() => { const mvn = spawn('mvn', [ 'package', '-f', @@ -235,8 +233,7 @@ class AwsInvokeLocal { mvn.on('close', () => { this.callJavaBridge(artifactPath, className, input).then(resolve); }); - }); - }); + })); } invokeLocalNodeJs(handlerPath, handlerName, event, customContext) { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 79655d1e5..8e73d67ec 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -3,11 +3,9 @@ const expect = require('chai').expect; const sinon = require('sinon'); const path = require('path'); -const BbPromise = require('bluebird'); const mockRequire = require('mock-require'); const EventEmitter = require('events'); -const fs = BbPromise.promisifyAll(require('graceful-fs')); -const fsExtra = require('fs-extra'); +const fse = require('fs-extra'); const AwsInvokeLocal = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); @@ -531,7 +529,7 @@ describe('AwsInvokeLocal', () => { }); }); - describe('#callJavaBridge', () => { + describe('#callJavaBridge()', () => { let awsInvokeLocalMocked; let writeChildStub; let endChildStub; @@ -591,11 +589,12 @@ describe('AwsInvokeLocal', () => { ); }); - describe('#invokeLocalJava', () => { + describe('#invokeLocalJava()', () => { const bridgePath = path.join(__dirname, 'java', 'target'); let callJavaBridgeStub; beforeEach(() => { + fse.mkdirsSync(bridgePath); callJavaBridgeStub = sinon.stub(awsInvokeLocal, 'callJavaBridge').resolves(); awsInvokeLocal.options = { stage: 'dev', @@ -607,20 +606,14 @@ describe('AwsInvokeLocal', () => { }, data: {}, }; - - serverless.cli = new CLI(serverless); - sinon.stub(serverless.cli, 'consoleLog'); }); afterEach(() => { - serverless.cli.consoleLog.restore(); awsInvokeLocal.callJavaBridge.restore(); - fsExtra.removeSync(bridgePath); + fse.removeSync(bridgePath); }); - it('should invoke callJavaBridge when bridge is built', () => { - fs.mkdirSync(bridgePath); - + it('should invoke callJavaBridge when bridge is built', () => awsInvokeLocal.invokeLocalJava( 'java', 'com.serverless.Handler', @@ -641,10 +634,10 @@ describe('AwsInvokeLocal', () => { }, }) )).to.be.equal(true); - }); - }); + }) + ); - describe('should build Java bridge', () => { + describe('when attempting to build the Java bridge', () => { let awsInvokeLocalMocked; let callJavaBridgeMockedStub; @@ -696,7 +689,6 @@ describe('AwsInvokeLocal', () => { __dirname, {} ).then(() => { - expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('Building Java bridge, first invocation might take a bit longer.'); //eslint-disable-line expect(callJavaBridgeMockedStub.calledOnce).to.be.equal(true); expect(callJavaBridgeMockedStub.calledWithExactly( __dirname, From a1cf9fd65edbda214196e090366c6a73def08568 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 18 Sep 2017 12:29:37 +0200 Subject: [PATCH 18/18] Minor fixes --- lib/plugins/aws/invokeLocal/index.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 37887cd36..2bdb17365 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -182,8 +182,8 @@ class AwsInvokeLocal { this.serverless.cli.log([ 'In order to get human-readable output,', - 'please implement "toString()" method of your "ApiGatewayResponse" object.', - ].join(' ')); + ' please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join('')); java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); @@ -223,16 +223,13 @@ class AwsInvokeLocal { path.join(javaBridgePath, 'pom.xml'), ]); - this.serverless.cli.log( - 'Building Java bridge, first invocation might take a bit longer.' - ); + this.serverless.cli + .log('Building Java bridge, first invocation might take a bit longer.'); mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); mvn.stdin.end(); - mvn.on('close', () => { - this.callJavaBridge(artifactPath, className, input).then(resolve); - }); + mvn.on('close', () => this.callJavaBridge(artifactPath, className, input).then(resolve)); })); }