Repository: localstack/localstack

Issue:
 Argument list too long when a lambda function event body is too large
I'm currently working on a process by which a kinesis stream triggers a lambda function to process a batch of records.  Whenever the `putRecords` method is used with a large amount of records, the call to trigger the lambda will fail b/c the argument list is too long.  The command is:

```
CONTAINER_ID="$(docker create  -e AWS_LAMBDA_FUNCTION_INVOKED_ARN="$AWS_LAMBDA_FUNCTION_INVOKED_ARN" -e AWS_LAMBDA_FUNCTION_NAME="$AWS_LAMBDA_FUNCTION_NAME" -e AWS_LAMBDA_EVENT_BODY="$AWS_LAMBDA_EVENT_BODY" -e AWS_LAMBDA_FUNCTION_VERSION="$AWS_LAMBDA_FUNCTION_VERSION" -e LOCALSTACK_HOSTNAME="$LOCALSTACK_HOSTNAME"  "lambci/lambda:nodejs8.10" "index.handler")"
```

Instead of sending the event body to the lambda via an argument list, the body should be written to a file that is then copied into the docker and then the command should reference the file  in the arguments list.

<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->


Discussion:

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_executors.py b/localstack/services/awslambda/lambda_executors.py
--- a/localstack/services/awslambda/lambda_executors.py
+++ b/localstack/services/awslambda/lambda_executors.py
@@ -127,9 +127,10 @@ def _store_logs(self, func_details, log_output, invocation_time):
             logEvents=log_events
         )
 
-    def run_lambda_executor(self, cmd, env_vars={}):
-        process = run(cmd, asynchronous=True, stderr=subprocess.PIPE, outfile=subprocess.PIPE, env_vars=env_vars)
-        result, log_output = process.communicate()
+    def run_lambda_executor(self, cmd, event=None, env_vars={}):
+        process = run(cmd, asynchronous=True, stderr=subprocess.PIPE, outfile=subprocess.PIPE, env_vars=env_vars,
+                      stdin=True)
+        result, log_output = process.communicate(input=event)
         result = to_str(result).strip()
         log_output = to_str(log_output).strip()
         return_code = process.returncode
@@ -166,6 +167,12 @@ def _docker_cmd(self):
         """ Return the string to be used for running Docker commands. """
         return config.DOCKER_CMD
 
+    def prepare_event(self, environment, event_body):
+        """ Return the event as a stdin string. """
+        # amend the environment variables for execution
+        environment['AWS_LAMBDA_EVENT_BODY'] = event_body
+        return None
+
     def _execute(self, func_arn, func_details, event, context=None, version=None):
 
         lambda_cwd = func_details.cwd
@@ -182,11 +189,10 @@ def _execute(self, func_arn, func_details, event, context=None, version=None):
             LOG.warning('Empty event body specified for invocation of Lambda "%s"' % func_arn)
             event = {}
         event_body = json.dumps(event)
+        stdin = self.prepare_event(environment, event_body)
 
         docker_host = config.DOCKER_HOST_FROM_CONTAINER
 
-        # amend the environment variables for execution
-        environment['AWS_LAMBDA_EVENT_BODY'] = event_body
         environment['HOSTNAME'] = docker_host
         environment['LOCALSTACK_HOSTNAME'] = docker_host
         if context:
@@ -199,6 +205,7 @@ def _execute(self, func_arn, func_details, event, context=None, version=None):
 
         # if running a Java Lambda, set up classpath arguments
         if runtime == LAMBDA_RUNTIME_JAVA8:
+            stdin = None
             # copy executor jar into temp directory
             target_file = os.path.join(lambda_cwd, os.path.basename(LAMBDA_EXECUTOR_JAR))
             if not os.path.exists(target_file):
@@ -214,7 +221,7 @@ def _execute(self, func_arn, func_details, event, context=None, version=None):
 
         # lambci writes the Lambda result to stdout and logs to stderr, fetch it from there!
         LOG.debug('Running lambda cmd: %s' % cmd)
-        result, log_output = self.run_lambda_executor(cmd, environment)
+        result, log_output = self.run_lambda_executor(cmd, stdin, environment)
         log_formatted = log_output.strip().replace('\n', '\n> ')
         LOG.debug('Lambda %s result / log output:\n%s\n>%s' % (func_arn, result.strip(), log_formatted))
         return result, log_output
@@ -264,14 +271,14 @@ def prepare_execution(self, func_arn, env_vars, runtime, command, handler, lambd
         event_file = os.path.join(lambda_cwd, LAMBDA_EVENT_FILE)
         if not has_been_invoked_before:
             # if this is the first invocation: copy the entire folder into the container
-            copy_command = '%s cp "%s/." "%s:/var/task"; ' % (docker_cmd, lambda_cwd, container_info.name)
+            copy_command = '%s cp "%s/." "%s:/var/task";' % (docker_cmd, lambda_cwd, container_info.name)
         elif os.path.exists(event_file):
             # otherwise, copy only the event file if it exists
-            copy_command = '%s cp "%s" "%s:/var/task"; ' % (docker_cmd, event_file, container_info.name)
+            copy_command = '%s cp "%s" "%s:/var/task";' % (docker_cmd, event_file, container_info.name)
 
         cmd = (
-            '%s'  # copy files command
-            '%s exec'
+            '%s'
+            ' %s exec'
             ' %s'  # env variables
             ' %s'  # container name
             ' %s'  # run cmd
@@ -540,6 +547,12 @@ def get_container_name(self, func_arn):
 
 class LambdaExecutorSeparateContainers(LambdaExecutorContainers):
 
+    def prepare_event(self, environment, event_body):
+
+        # Tell Lambci to use STDIN for the event
+        environment['DOCKER_LAMBDA_USE_STDIN'] = '1'
+        return event_body.encode()
+
     def prepare_execution(self, func_arn, env_vars, runtime, command, handler, lambda_cwd):
         entrypoint = ''
         if command:
@@ -555,20 +568,20 @@ def prepare_execution(self, func_arn, env_vars, runtime, command, handler, lambd
 
         if config.LAMBDA_REMOTE_DOCKER:
             cmd = (
-                'CONTAINER_ID="$(docker create'
+                'CONTAINER_ID="$(docker create -i'
                 ' %s'
                 ' %s'
                 ' %s'  # network
                 ' "lambci/lambda:%s" %s'
                 ')";'
                 '%s cp "%s/." "$CONTAINER_ID:/var/task"; '
-                '%s start -a "$CONTAINER_ID";'
+                '%s start -ai "$CONTAINER_ID";'
             ) % (entrypoint, env_vars_string, network_str, runtime, command, docker_cmd, lambda_cwd, docker_cmd)
         else:
             lambda_cwd_on_host = self.get_host_path_for_path_in_docker(lambda_cwd)
             cmd = (
-                '%s run'
-                '%s -v "%s":/var/task'
+                '%s run -i'
+                ' %s -v "%s":/var/task'
                 ' %s'
                 ' %s'  # network
                 ' --rm'
diff --git a/localstack/utils/server/multiserver.py b/localstack/utils/server/multiserver.py
--- a/localstack/utils/server/multiserver.py
+++ b/localstack/utils/server/multiserver.py
@@ -95,7 +95,7 @@ def start_server_process(port):
     thread.start()
     TMP_THREADS.append(thread)
     config['thread'] = thread
-    wait_for_port_open(port, retries=10, sleep_time=1)
+    wait_for_port_open(port, retries=20, sleep_time=1)
     return thread
 
 


Test patch: