Skip to content

Commit be857c5

Browse files
authored
minor fixes to lambda executor (localstack#1443)
1 parent 9abc55d commit be857c5

4 files changed

Lines changed: 57 additions & 29 deletions

File tree

localstack/config.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@
7373
EXTRA_CORS_EXPOSE_HEADERS = os.environ.get('EXTRA_CORS_EXPOSE_HEADERS', '').strip()
7474

7575

76+
def has_docker():
77+
try:
78+
subprocess.check_output('docker ps', shell=True)
79+
return True
80+
except Exception:
81+
return False
82+
83+
7684
def is_linux():
7785
try:
7886
out = subprocess.check_output('uname -a', shell=True)
@@ -85,9 +93,9 @@ def is_linux():
8593
# whether to use Lambda functions in a Docker container
8694
LAMBDA_EXECUTOR = os.environ.get('LAMBDA_EXECUTOR', '').strip()
8795
if not LAMBDA_EXECUTOR:
88-
LAMBDA_EXECUTOR = 'local'
89-
if is_linux():
90-
LAMBDA_EXECUTOR = 'docker'
96+
LAMBDA_EXECUTOR = 'docker'
97+
if not has_docker():
98+
LAMBDA_EXECUTOR = 'local'
9199

92100

93101
# Fallback URL to use when a non-existing Lambda is invoked. If this matches

localstack/services/awslambda/lambda_api.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -497,24 +497,32 @@ def set_archive_code(code, lambda_name, zip_file_content=None):
497497
def set_function_code(code, lambda_name, lambda_cwd=None):
498498

499499
def generic_handler(event, context):
500-
raise ClientError(('Unable to find executor for Lambda function "%s". ' +
501-
'Note that Node.js and .NET Core Lambdas currently require LAMBDA_EXECUTOR=docker') % lambda_name)
500+
raise ClientError(('Unable to find executor for Lambda function "%s". Note that ' +
501+
'Node.js, Golang, and .Net Core Lambdas currently require LAMBDA_EXECUTOR=docker') % lambda_name)
502502

503503
arn = func_arn(lambda_name)
504504
lambda_details = arn_to_lambda[arn]
505505
runtime = lambda_details.runtime
506506
lambda_environment = lambda_details.envvars
507507
handler_name = lambda_details.handler or LAMBDA_DEFAULT_HANDLER
508+
code_passed = code
509+
code = code or lambda_details.code
508510
is_local_mount = code.get('S3Bucket') == BUCKET_MARKER_LOCAL
511+
zip_file_content = None
509512

510-
lambda_cwd = lambda_cwd or set_archive_code(code, lambda_name)
511-
512-
# Save the zip file to a temporary file that the lambda executors can reference
513-
zip_file_content = get_zip_bytes(code)
513+
if code_passed:
514+
lambda_cwd = lambda_cwd or set_archive_code(code_passed, lambda_name)
515+
# Save the zip file to a temporary file that the lambda executors can reference
516+
zip_file_content = get_zip_bytes(code_passed)
517+
else:
518+
lambda_cwd = lambda_cwd or lambda_details.cwd
514519

515520
# get local lambda working directory
516521
tmp_file = '%s/%s' % (lambda_cwd, LAMBDA_ZIP_FILE_NAME)
517522

523+
if not zip_file_content:
524+
zip_file_content = load_file(tmp_file, mode='rb')
525+
518526
# Set the appropriate lambda handler.
519527
lambda_handler = generic_handler
520528
if runtime == LAMBDA_RUNTIME_JAVA8:
@@ -545,8 +553,7 @@ def generic_handler(event, context):
545553
if os.path.isfile(main_file):
546554
# make sure the file is actually readable, then read contents
547555
ensure_readable(main_file)
548-
with open(main_file, 'rb') as file_obj:
549-
zip_file_content = file_obj.read()
556+
zip_file_content = load_file(main_file, mode='rb')
550557
else:
551558
# Raise an error if (1) this is not a local mount lambda, or (2) we're
552559
# running Lambdas locally (not in Docker), or (3) we're using remote Docker.
@@ -677,10 +684,14 @@ def create_function():
677684
func_details.timeout = data.get('Timeout', LAMBDA_DEFAULT_TIMEOUT)
678685
func_details.role = data['Role']
679686
func_details.memory_size = data.get('MemorySize')
680-
result = set_function_code(data['Code'], lambda_name)
687+
func_details.code = data['Code']
688+
result = set_function_code(func_details.code, lambda_name)
681689
if isinstance(result, Response):
682690
del arn_to_lambda[arn]
683691
return result
692+
# remove content from code attribute, if present
693+
func_details.code.pop('ZipFile', None)
694+
# prepare result
684695
result.update(format_func_details(func_details))
685696
if data.get('Publish', False):
686697
result['Version'] = publish_new_function_version(arn)['Version']
@@ -896,7 +907,7 @@ def _create_response(result, status_code=200):
896907
details[key] = result[key]
897908
# Try to parse parse payload as JSON
898909
payload = details['Payload']
899-
if payload and isinstance(payload, (str, bytes)) and payload[0] in ('[', '{'):
910+
if payload and isinstance(payload, (str, bytes)) and payload[0] in ('[', '{', '"'):
900911
try:
901912
details['Payload'] = json.loads(details['Payload'])
902913
except Exception:

localstack/utils/aws/aws_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def __init__(self, arn):
174174
self.description = ''
175175
self.role = None
176176
self.memory_size = None
177+
self.code = None
177178

178179
def get_version(self, version):
179180
return self.versions.get(version)

localstack/utils/common.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
DOWNLOAD_CHUNK_SIZE = 1024 * 1024
5656

5757
# set up logger
58-
LOGGER = logging.getLogger(__name__)
58+
LOG = logging.getLogger(__name__)
5959

6060
# flag to indicate whether we've received and processed the stop signal
6161
INFRA_STOPPED = False
@@ -92,12 +92,12 @@ def run(self):
9292
self.func(self.params)
9393
except Exception:
9494
if not self.quiet:
95-
LOGGER.warning('Thread run method %s(%s) failed: %s' %
95+
LOG.warning('Thread run method %s(%s) failed: %s' %
9696
(self.func, self.params, traceback.format_exc()))
9797

9898
def stop(self, quiet=False):
9999
if not quiet and not self.quiet:
100-
LOGGER.warning('Not implemented: FuncThread.stop(..)')
100+
LOG.warning('Not implemented: FuncThread.stop(..)')
101101

102102

103103
class ShellCommandThread(FuncThread):
@@ -142,9 +142,9 @@ def convert_line(line):
142142
self.process.communicate()
143143
except Exception as e:
144144
if self.process and not self.quiet:
145-
LOGGER.warning('Shell command error "%s": %s' % (e, self.cmd))
145+
LOG.warning('Shell command error "%s": %s' % (e, self.cmd))
146146
if self.process and not self.quiet and self.process.returncode != 0:
147-
LOGGER.warning('Shell command exit code "%s": %s' % (self.process.returncode, self.cmd))
147+
LOG.warning('Shell command exit code "%s": %s' % (self.process.returncode, self.cmd))
148148

149149
def is_killed(self):
150150
if not self.process:
@@ -164,7 +164,7 @@ def stop(self, quiet=False):
164164
import psutil
165165

166166
if not self.process:
167-
LOGGER.warning("No process found for command '%s'" % self.cmd)
167+
LOG.warning("No process found for command '%s'" % self.cmd)
168168
return
169169

170170
parent_pid = self.process.pid
@@ -176,7 +176,7 @@ def stop(self, quiet=False):
176176
self.process = None
177177
except Exception:
178178
if not quiet:
179-
LOGGER.warning('Unable to kill process with pid %s' % parent_pid)
179+
LOG.warning('Unable to kill process with pid %s' % parent_pid)
180180

181181

182182
class JsonObject(object):
@@ -332,6 +332,14 @@ def in_docker():
332332
return config.in_docker()
333333

334334

335+
def has_docker():
336+
try:
337+
run('docker ps')
338+
return True
339+
except Exception:
340+
return False
341+
342+
335343
def is_port_open(port_or_url, http_path=None, expect_success=True):
336344
port = port_or_url
337345
host = 'localhost'
@@ -413,7 +421,7 @@ def merge_recursive(source, destination):
413421
merge_recursive(value, node)
414422
else:
415423
if not isinstance(destination, dict):
416-
LOGGER.warning('Destination for merging %s=%s is not dict: %s' %
424+
LOG.warning('Destination for merging %s=%s is not dict: %s' %
417425
(key, value, destination))
418426
destination[key] = value
419427
return destination
@@ -475,7 +483,7 @@ def ensure_readable(file_path, default_perms=None):
475483
with open(file_path, 'rb'):
476484
pass
477485
except Exception:
478-
LOGGER.info('Updating permissions as file is currently not readable: %s' % file_path)
486+
LOG.info('Updating permissions as file is currently not readable: %s' % file_path)
479487
os.chmod(file_path, default_perms)
480488

481489

@@ -522,19 +530,19 @@ def download(url, path, verify_ssl=True):
522530
try:
523531
if not os.path.exists(os.path.dirname(path)):
524532
os.makedirs(os.path.dirname(path))
525-
LOGGER.debug('Starting download from %s to %s (%s bytes)' % (url, path, r.headers.get('content-length')))
533+
LOG.debug('Starting download from %s to %s (%s bytes)' % (url, path, r.headers.get('content-length')))
526534
with open(path, 'wb') as f:
527535
for chunk in r.iter_content(DOWNLOAD_CHUNK_SIZE):
528536
total += len(chunk)
529537
if chunk: # filter out keep-alive new chunks
530538
f.write(chunk)
531-
LOGGER.debug('Writing %s bytes (total %s) to %s' % (len(chunk), total, path))
539+
LOG.debug('Writing %s bytes (total %s) to %s' % (len(chunk), total, path))
532540
else:
533-
LOGGER.debug('Empty chunk %s (total %s) from %s' % (chunk, total, url))
541+
LOG.debug('Empty chunk %s (total %s) from %s' % (chunk, total, url))
534542
f.flush()
535543
os.fsync(f)
536544
finally:
537-
LOGGER.debug('Done downloading %s, response code %s' % (url, r.status_code))
545+
LOG.debug('Done downloading %s, response code %s' % (url, r.status_code))
538546
r.close()
539547
s.close()
540548

@@ -684,7 +692,7 @@ def unzip(path, target_dir):
684692
try:
685693
zip_ref = zipfile.ZipFile(path, 'r')
686694
except Exception as e:
687-
LOGGER.warning('Unable to open zip file: %s: %s' % (path, e))
695+
LOG.warning('Unable to open zip file: %s: %s' % (path, e))
688696
raise e
689697
# Make sure to preserve file permissions in the zip file
690698
# https://www.burgundywall.com/post/preserving-file-perms-with-python-zipfile-module
@@ -792,12 +800,12 @@ def generate_ssl_cert(target_file=None, overwrite=False, random=False):
792800
return file_content
793801

794802

795-
def run_safe(_python_lambda, print_error=True, **kwargs):
803+
def run_safe(_python_lambda, print_error=False, **kwargs):
796804
try:
797805
return _python_lambda(**kwargs)
798806
except Exception as e:
799807
if print_error:
800-
print('Unable to execute function: %s' % e)
808+
LOG.warning('Unable to execute function: %s' % e)
801809

802810

803811
def run_cmd_safe(**kwargs):

0 commit comments

Comments
 (0)