Repository: localstack/localstack

Issue:
Allow Lambda scripts to use __file__
Currently we are `exec()`ing the Lambda code to get the handler function. This prevents Lambdas from reflecting on their script location via `__file__`. This needs to be fixed.


Discussion:

Resolving patch:
diff --git a/localstack/mock/lambda_api.py b/localstack/mock/lambda_api.py
--- a/localstack/mock/lambda_api.py
+++ b/localstack/mock/lambda_api.py
@@ -8,6 +8,7 @@
 import logging
 import base64
 import threading
+import imp
 from flask import Flask, jsonify, request, make_response
 from datetime import datetime
 from localstack.constants import *
@@ -19,6 +20,7 @@
 PATH_ROOT = '/2015-03-31'
 ARCHIVE_FILE_PATTERN = '/tmp/lambda.handler.*.jar'
 EVENT_FILE_PATTERN = '/tmp/lambda.event.*.json'
+LAMBDA_SCRIPT_PATTERN = '/tmp/lambda_script_*.py'
 LAMBDA_EXECUTOR_JAR = os.path.join(LOCALSTACK_ROOT_FOLDER, 'localstack',
     'mock', 'target', 'lambda-executor-1.0-SNAPSHOT.jar')
 LAMBDA_EXECUTOR_CLASS = 'com.atlassian.LambdaExecutor'
@@ -140,12 +142,16 @@ def exec_lambda_code(script, handler_function='handler', lambda_cwd=None):
         cwd_mutex.acquire()
         previous_cwd = os.getcwd()
         os.chdir(lambda_cwd)
-    # WARNING: we can only do exec(..) for controlled test environments - it's dangerous!
-    local_vars = {}
-    # make sure os.environ[...] is available in the lambda context
-    local_vars['os'] = os
+    # generate lambda file name
+    lambda_id = 'l_%s' % short_uid()
+    lambda_file = LAMBDA_SCRIPT_PATTERN.replace('*', lambda_id)
+    save_file(lambda_file, script)
+    # delete temporary .py and .pyc files on exit
+    TMP_FILES.append(lambda_file)
+    TMP_FILES.append('%sc' % lambda_file)
     try:
-        exec(script, local_vars)
+        handler_module = imp.load_source(lambda_id, lambda_file)
+        module_vars = handler_module.__dict__
     except Exception, e:
         print('ERROR: Unable to exec: %s %s' % (script, traceback.format_exc(e)))
         raise e
@@ -153,7 +159,7 @@ def exec_lambda_code(script, handler_function='handler', lambda_cwd=None):
         if lambda_cwd:
             os.chdir(previous_cwd)
             cwd_mutex.release()
-    return local_vars[handler_function]
+    return module_vars[handler_function]
 
 
 def set_function_code(code, lambda_name):
diff --git a/localstack/utils/common.py b/localstack/utils/common.py
--- a/localstack/utils/common.py
+++ b/localstack/utils/common.py
@@ -49,17 +49,20 @@ def stop(self, quiet=False):
 
 
 class ShellCommandThread (FuncThread):
-    def __init__(self, cmd, params={}, outfile=None, env_vars={}, stdin=False, quiet=True):
+    def __init__(self, cmd, params={}, outfile=None, env_vars={}, stdin=False,
+            quiet=True, inherit_cwd=False):
         self.cmd = cmd
         self.process = None
         self.outfile = outfile
         self.stdin = stdin
         self.env_vars = env_vars
+        self.inherit_cwd = inherit_cwd
         FuncThread.__init__(self, self.run_cmd, params, quiet=quiet)
 
     def run_cmd(self, params):
         try:
-            self.process = run(self.cmd, async=True, stdin=self.stdin, outfile=self.outfile, env_vars=self.env_vars)
+            self.process = run(self.cmd, async=True, stdin=self.stdin, outfile=self.outfile,
+                env_vars=self.env_vars, inherit_cwd=self.inherit_cwd)
             if self.outfile:
                 self.process.wait()
             else:
@@ -184,7 +187,8 @@ def cleanup_resources():
     cleanup_threads_and_processes()
 
 
-def run(cmd, cache_duration_secs=0, print_error=True, async=False, stdin=False, outfile=None, env_vars=None):
+def run(cmd, cache_duration_secs=0, print_error=True, async=False, stdin=False, outfile=None,
+        env_vars=None, inherit_cwd=False):
     # don't use subprocess module as it is not thread-safe
     # http://stackoverflow.com/questions/21194380/is-subprocess-popen-not-thread-safe
     # import subprocess
@@ -196,26 +200,27 @@ def run(cmd, cache_duration_secs=0, print_error=True, async=False, stdin=False,
 
     def do_run(cmd):
         try:
+            cwd = os.getcwd() if inherit_cwd else None
             if not async:
                 if stdin:
                     return subprocess.check_output(cmd, shell=True,
-                        stderr=subprocess.STDOUT, stdin=subprocess.PIPE, env=env_dict)
-                return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, env=env_dict)
-            FNULL = open(os.devnull, 'w')
+                        stderr=subprocess.STDOUT, stdin=subprocess.PIPE, env=env_dict, cwd=cwd)
+                return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, env=env_dict, cwd=cwd)
             # subprocess.Popen is not thread-safe, hence use a mutex here..
-            mutex_popen.acquire()
-            if stdin:
-                process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env_dict)
-            else:
-                out = open(outfile, 'wb') if outfile else FNULL
-                process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=out, env=env_dict)
-            return process
+            try:
+                mutex_popen.acquire()
+                stdin_arg = subprocess.PIPE if stdin else None
+                stdout_arg = (open(outfile, 'wb') if isinstance(outfile, basestring) else
+                    outfile if outfile else open(os.devnull, 'w'))
+                process = subprocess.Popen(cmd, shell=True, stdin=stdin_arg,
+                    stderr=subprocess.STDOUT, stdout=stdout_arg, env=env_dict, cwd=cwd)
+                return process
+            finally:
+                mutex_popen.release()
         except subprocess.CalledProcessError, e:
             if print_error:
                 print("ERROR: '%s': %s" % (cmd, e.output))
             raise e
-        finally:
-            mutex_popen.release()
     if cache_duration_secs <= 0:
         return do_run(cmd)
     hash = md5(cmd)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -76,7 +76,7 @@ def run(self):
 
     setup(
         name='localstack',
-        version='0.2.3',
+        version='0.2.4',
         description='Provides an easy-to-use test/mocking framework for developing Cloud applications',
         author='Waldemar Hummer (Atlassian)',
         author_email='waldemar.hummer@gmail.com',


Test patch: