Skip to content
This repository was archived by the owner on Nov 11, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ ADD localstack/ext/ localstack/ext/
# install dependencies
RUN make install

# build the java code and copy it to the dest
RUN cd localstack/ext/java; mvn -Pfatjar -DskipTests -q clean package
RUN cp localstack/ext/java/target/localstack-utils-*-fat.jar localstack/infra/localstack-utils-fat.jar

# add files required to run "make init"
ADD localstack/package.json localstack/package.json
ADD localstack/services/__init__.py localstack/services/install.py localstack/services/
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
IMAGE_NAME ?= localstack/localstack
IMAGE_NAME_BASE ?= localstack/java-maven-node-python
IMAGE_NAME ?= blafrisch/localstack

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer our quay.io repos or if this is meant to be truly public, create an agari/ repo on docker hub.

IMAGE_NAME_BASE ?= blafrisch/localstack-base
IMAGE_TAG ?= $(shell cat localstack/constants.py | grep '^VERSION =' | sed "s/VERSION = ['\"]\(.*\)['\"].*/\1/")
VENV_DIR ?= .venv
VENV_RUN = . $(VENV_DIR)/bin/activate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public static void main(String[] args) throws Exception {
inputObject = deserialisedInput.get();
}
} else {
if (records.stream().anyMatch(record -> record.containsKey("Kinesis"))) {
if (records.stream().anyMatch(record -> record.containsKey("Kinesis")) ||
records.stream().anyMatch(record -> record.containsKey("kinesis"))) {
KinesisEvent kinesisEvent = new KinesisEvent();
inputObject = kinesisEvent;
kinesisEvent.setRecords(new LinkedList<>());
Expand Down Expand Up @@ -112,6 +113,7 @@ public static void main(String[] args) throws Exception {
new StringInputStream(fileContent), os, ctx);
System.out.println(os);
}
System.exit(0);
}

private static Optional<Object> getInputObject(ObjectMapper mapper, String objectString, Object handler) {
Expand Down
33 changes: 30 additions & 3 deletions localstack/services/awslambda/lambda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import sys
import json
import uuid
import tempfile
import time
import traceback
import logging
import base64
import threading
import imp
import re
import zipfile
from io import BytesIO
from datetime import datetime
from six import iteritems
Expand All @@ -28,6 +30,7 @@
LAMBDA_RUNTIME_JAVA8,
LAMBDA_RUNTIME_DOTNETCORE2,
LAMBDA_RUNTIME_GOLANG)
from localstack.services.install import INSTALL_PATH_LOCALSTACK_FAT_JAR
from localstack.utils.common import (to_str, load_file, save_file, TMP_FILES, ensure_readable,
mkdir, unzip, is_zip_file, run, short_uid, is_jar_archive, timestamp, TIMESTAMP_FORMAT_MILLIS)
from localstack.utils.aws import aws_stack, aws_responses
Expand Down Expand Up @@ -373,7 +376,16 @@ def get_java_handler(zip_file_content, handler, main_file):

:returns: function or flask.Response
"""
if is_jar_archive(zip_file_content):
if is_zip_archive(zip_file_content):
def execute(event, context):
with zipfile.ZipFile(LAMBDA_ZIP_FILE_NAME, 'r') as zf:
zf.extractall('.')
result, log_output = lambda_executors.EXECUTOR_LOCAL.execute_java_lambda(
event, context, handler=handler, main_file=main_file,
classpath='.:lib/*:%s' % INSTALL_PATH_LOCALSTACK_FAT_JAR)
return result
return execute
elif is_jar_archive(zip_file_content):
def execute(event, context):
result, log_output = lambda_executors.EXECUTOR_LOCAL.execute_java_lambda(
event, context, handler=handler, main_file=main_file)
Expand All @@ -383,6 +395,20 @@ def execute(event, context):
'ZIP file for the java8 runtime not yet supported.', 400, error_type='ValidationError')


def is_zip_archive(content):
# TODO: Perhaps there's a better way rather than looking for files in 'lib/'
try:
with tempfile.NamedTemporaryFile() as tf:
tf.write(content)
tf.flush()
with zipfile.ZipFile(tf.name, 'r') as zf:
if 'lib/' in [x.filename for x in zf.infolist()]:
return True
except Exception:
pass
return False


def set_function_code(code, lambda_name):

def generic_handler(event, context):
Expand Down Expand Up @@ -521,7 +547,8 @@ def create_function():
func_details.versions = {'$LATEST': {'CodeSize': 50}}
func_details.handler = data['Handler']
func_details.runtime = data['Runtime']
func_details.envvars = data.get('Environment', {}).get('Variables', {})
# Copy appears to be necessary for it to appear in the execution
func_details.envvars = data.get('Environment', {}).get('Variables', {}).copy()
func_details.timeout = data.get('Timeout')
result = set_function_code(data['Code'], lambda_name)
if isinstance(result, Response):
Expand Down Expand Up @@ -689,7 +716,7 @@ def update_function_configuration(function):
if data.get('Runtime'):
lambda_details.runtime = data['Runtime']
if data.get('Environment'):
lambda_details.envvars = data.get('Environment', {}).get('Variables', {})
lambda_details.envvars = data.get('Environment', {}).get('Variables', {}).copy()
if data.get('Timeout'):
lambda_details.timeout = data['Timeout']
result = {}
Expand Down
28 changes: 18 additions & 10 deletions localstack/services/awslambda/lambda_executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# for Python 2.7
from pipes import quote as cmd_quote
from localstack import config
from localstack.utils.common import run, TMP_FILES, short_uid, save_file, to_str, cp_r
from localstack.utils.common import run, TMP_FILES, short_uid, save_file, cp_r
from localstack.services.install import INSTALL_PATH_LOCALSTACK_FAT_JAR

# constants
Expand Down Expand Up @@ -60,13 +60,14 @@ def run_lambda_executor(self, cmd, env_vars={}, asynchronous=False):
result = '{"asynchronous": "%s"}' % asynchronous
log_output = 'Lambda executed asynchronously'
else:
return_code = process.wait()
result = to_str(process.stdout.read())
log_output = to_str(process.stderr.read())

if return_code != 0:
raise Exception('Lambda process returned error status code: %s. Output:\n%s' %
(return_code, log_output))
# This was using process.wait() but if the process LambdaExecutor wraps logs to stdout a lot then the OS
# can permanently block. See: https://docs.python.org/2/library/subprocess.html#subprocess.Popen.wait
try:
result, log_output = process.communicate()
except Exception, e:
LOG.error('Lambda process returned error status code. Result:\n%s\nOutput:\n%s' %
(result, log_output))
raise e
return result, log_output


Expand Down Expand Up @@ -458,7 +459,12 @@ def do_execute():
if lambda_cwd:
os.chdir(lambda_cwd)
if environment:
# TODO: Another bug; this will add to the environment but never reset after
# Fix by storing old environ and reverting after run regardless of outcome
os.environ.update(environment)
LOG.debug('cwd: %s' % lambda_cwd)
LOG.debug('cwd content: %s' % os.listdir('.'))
LOG.debug('environment: %s' % os.environ)
result = lambda_function(event, context)
queue.put(result)

Expand All @@ -469,13 +475,15 @@ def do_execute():
log_output = ''
return result, log_output

def execute_java_lambda(self, event, context, handler, main_file):
def execute_java_lambda(self, event, context, handler, main_file, classpath=None):
event_file = EVENT_FILE_PATTERN.replace('*', short_uid())
save_file(event_file, json.dumps(event))
TMP_FILES.append(event_file)
class_name = handler.split('::')[0]
classpath = '%s:%s' % (LAMBDA_EXECUTOR_JAR, main_file)
if classpath is None:
classpath = '%s:%s' % (LAMBDA_EXECUTOR_JAR, main_file)
cmd = 'java -cp %s %s %s %s' % (classpath, LAMBDA_EXECUTOR_CLASS, class_name, event_file)
LOG.debug('Lambda cmd: %s' % cmd)
asynchronous = False
# flip asynchronous flag depending on origin
if 'Records' in event:
Expand Down
1 change: 1 addition & 0 deletions localstack/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ def _unzip_file_entry(zip_ref, file_entry, target_dir):

def is_jar_archive(content):
has_class_content = False
# This is abhorent; it's looking at binary data for the string 'class' and if it's there it has class content
try:
has_class_content = 'class' in content
except TypeError:
Expand Down
33 changes: 1 addition & 32 deletions tests/integration/test_elasticsearch.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import time
from botocore.exceptions import ClientError
from nose.tools import assert_raises, assert_equal, assert_true, assert_false
from nose.tools import assert_equal, assert_true
from localstack.utils.aws import aws_stack
from localstack.utils.common import safe_requests as requests

Expand Down Expand Up @@ -52,36 +51,6 @@ def delete_document(id):
return resp


def test_domain_creation():
es_client = aws_stack.connect_to_service('es')

# create ES domain
es_client.create_elasticsearch_domain(DomainName=TEST_DOMAIN_NAME)
assert_true(TEST_DOMAIN_NAME in
[d['DomainName'] for d in es_client.list_domain_names()['DomainNames']])

# make sure we cannot re-create same domain name
assert_raises(ClientError, es_client.create_elasticsearch_domain, DomainName=TEST_DOMAIN_NAME)

# get domain status
status = es_client.describe_elasticsearch_domain(DomainName=TEST_DOMAIN_NAME)
assert_equal(status['DomainStatus']['DomainName'], TEST_DOMAIN_NAME)
assert_true(status['DomainStatus']['Created'])
assert_false(status['DomainStatus']['Processing'])
assert_false(status['DomainStatus']['Deleted'])
assert_equal(status['DomainStatus']['Endpoint'], aws_stack.get_elasticsearch_endpoint())
assert_true(status['DomainStatus']['EBSOptions']['EBSEnabled'])

# make sure we can fake adding tags to a domain
response = es_client.add_tags(ARN='string', TagList=[{'Key': 'SOME_TAG', 'Value': 'SOME_VALUE'}])
assert_equal(200, response['ResponseMetadata']['HTTPStatusCode'])

# make sure domain deletion works
es_client.delete_elasticsearch_domain(DomainName=TEST_DOMAIN_NAME)
assert_false(TEST_DOMAIN_NAME in
[d['DomainName'] for d in es_client.list_domain_names()['DomainNames']])


def test_elasticsearch_get_document():
article_path = '{}/{}/employee/{}?pretty'.format(
ES_URL, TEST_INDEX, TEST_DOC_ID)
Expand Down