Repository: serverless/serverless (JavaScript)
Date: 2019-04-03 13:33:58+00:00
Task type: Feature

Issue:
Ability to pass docker run arguments during invoke local docker 
<!--
1. If you have a question and not a feature request please ask first at http://forum.serverless.com
2. Please check if an issue already exists. This feature may have already been requested
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal
 
## Description

* **UseCase**: Debug lambda function with command `local invoke --docker`
* **Implementation**: Use docker run argument [`-p host_debug_port:container_debug_port`](https://docs.docker.com/v17.09/engine/userguide/networking/default_network/binding/)

* If there is additional config how would it look

```
sls invoke local -f hello  --docker --docker-arg "-p 5898:5898"
```


Similar or dependent issues: None



Issue discussion:

Solution patch:
diff --git a/docs/providers/aws/cli-reference/invoke-local.md b/docs/providers/aws/cli-reference/invoke-local.md
index f360552620a..e9a51687d72 100644
--- a/docs/providers/aws/cli-reference/invoke-local.md
+++ b/docs/providers/aws/cli-reference/invoke-local.md
@@ -31,6 +31,7 @@ serverless invoke local --function functionName
 * `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `<name>=<value>`. Can be repeated for more than one environment variable.
 * `--docker` Enable docker support for NodeJS/Python/Ruby/Java. Enabled by default for other
   runtimes.
+* `--docker-arg` Pass additional arguments to docker run command when `--docker` is option used. e.g. `--docker-arg '-p 9229:9229' --docker-arg '-v /var:/host_var'`
 
 ## Environment
 
diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js
index 26140da9a6f..e956e4eccc8 100644
--- a/lib/plugins/aws/invokeLocal/index.js
+++ b/lib/plugins/aws/invokeLocal/index.js
@@ -328,10 +328,12 @@ class AwsInvokeLocal {
       .then((results) => new BbPromise((resolve, reject) => {
         const imageName = results[2];
         const artifactPath = results[3];
-        const dockerArgs = [
-          'run', '--rm', '-v', `${artifactPath}:/var/task`, imageName,
-          handler, JSON.stringify(this.options.data),
-        ];
+        const dockerArgsFromOptions = this.getDockerArgsFromOptions();
+        const dockerArgs = _.concat([
+          'run', '--rm', '-v', `${artifactPath}:/var/task`,
+        ], dockerArgsFromOptions, [
+          imageName, handler, JSON.stringify(this.options.data),
+        ]);
         const docker = spawn('docker', dockerArgs);
         docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString()));
         docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString()));
@@ -339,6 +341,15 @@ class AwsInvokeLocal {
       }));
   }
 
+  getDockerArgsFromOptions() {
+    const dockerArgOptions = this.options['docker-arg'];
+    const dockerArgsFromOptions = _.flatMap(_.concat(dockerArgOptions || []), (dockerArgOption) => {
+      const splitItems = dockerArgOption.split(' ');
+      return [splitItems[0], splitItems.slice(1).join(' ')];
+    });
+    return dockerArgsFromOptions;
+  }
+
   invokeLocalPython(runtime, handlerPath, handlerName, event, context) {
     const input = JSON.stringify({
       event: event || {},
diff --git a/lib/plugins/invoke/invoke.js b/lib/plugins/invoke/invoke.js
index d07db02163c..f9c5405b6af 100644
--- a/lib/plugins/invoke/invoke.js
+++ b/lib/plugins/invoke/invoke.js
@@ -88,6 +88,9 @@ class Invoke {
                 shortcut: 'e',
               },
               docker: { usage: 'Flag to turn on docker use for node/python/ruby/java' },
+              'docker-arg': {
+                usage: 'Arguments to docker run command. e.g. --docker-arg "-p 9229:9229"',
+              },
             },
 
           },


Test patch:
diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js
index c88c8ec316f..d858494737a 100644
--- a/lib/plugins/aws/invokeLocal/index.test.js
+++ b/lib/plugins/aws/invokeLocal/index.test.js
@@ -1161,6 +1161,7 @@ describe('AwsInvokeLocal', () => {
           runtime: 'nodejs8.10',
         },
         data: {},
+        'docker-arg': '-p 9292:9292',
       };
     });
 
@@ -1190,6 +1191,8 @@ describe('AwsInvokeLocal', () => {
           '--rm',
           '-v',
           'servicePath:/var/task',
+          '-p',
+          '9292:9292',
           'sls-docker',
           'handler.hello',
           '{}',
@@ -1197,4 +1200,38 @@ describe('AwsInvokeLocal', () => {
       })
     );
   });
+
+  describe('#getDockerArgsFromOptions', () => {
+    it('returns empty list when docker-arg option is absent', () => {
+      delete awsInvokeLocal.options['docker-arg'];
+
+      const dockerArgsFromOptions = awsInvokeLocal.getDockerArgsFromOptions();
+
+      expect(dockerArgsFromOptions).to.eql([]);
+    });
+
+    it('returns arg split by space when single docker-arg option is present', () => {
+      awsInvokeLocal.options['docker-arg'] = '-p 9229:9229';
+
+      const dockerArgsFromOptions = awsInvokeLocal.getDockerArgsFromOptions();
+
+      expect(dockerArgsFromOptions).to.eql(['-p', '9229:9229']);
+    });
+
+    it('returns args split by space when multiple docker-arg options are present', () => {
+      awsInvokeLocal.options['docker-arg'] = ['-p 9229:9229', '-v /var/logs:/host-var-logs'];
+
+      const dockerArgsFromOptions = awsInvokeLocal.getDockerArgsFromOptions();
+
+      expect(dockerArgsFromOptions).to.eql(['-p', '9229:9229', '-v', '/var/logs:/host-var-logs']);
+    });
+
+    it('returns arg split only by first space when docker-arg option has multiple space', () => {
+      awsInvokeLocal.options['docker-arg'] = '-v /My Docs:/docs';
+
+      const dockerArgsFromOptions = awsInvokeLocal.getDockerArgsFromOptions();
+
+      expect(dockerArgsFromOptions).to.eql(['-v', '/My Docs:/docs']);
+    });
+  });
 });