Lambda improvements (#1344)
* Revamped the lambda function storage to do versioning. * Flake8 * . * Fixes * Swapped around an if
This commit is contained in:
parent
cfc994d0ae
commit
d5ee48eedd
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import docker.errors
|
import docker.errors
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -17,18 +18,23 @@ import tarfile
|
|||||||
import calendar
|
import calendar
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
import weakref
|
||||||
import requests.adapters
|
import requests.adapters
|
||||||
|
|
||||||
import boto.awslambda
|
import boto.awslambda
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
|
from moto.core.exceptions import RESTError
|
||||||
from moto.core.utils import unix_time_millis
|
from moto.core.utils import unix_time_millis
|
||||||
from moto.s3.models import s3_backend
|
from moto.s3.models import s3_backend
|
||||||
from moto.logs.models import logs_backends
|
from moto.logs.models import logs_backends
|
||||||
from moto.s3.exceptions import MissingBucket, MissingKey
|
from moto.s3.exceptions import MissingBucket, MissingKey
|
||||||
from moto import settings
|
from moto import settings
|
||||||
|
from .utils import make_function_arn
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ACCOUNT_ID = '123456789012'
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
@ -121,7 +127,7 @@ class _DockerDataVolumeContext:
|
|||||||
|
|
||||||
|
|
||||||
class LambdaFunction(BaseModel):
|
class LambdaFunction(BaseModel):
|
||||||
def __init__(self, spec, region, validate_s3=True):
|
def __init__(self, spec, region, validate_s3=True, version=1):
|
||||||
# required
|
# required
|
||||||
self.region = region
|
self.region = region
|
||||||
self.code = spec['Code']
|
self.code = spec['Code']
|
||||||
@ -161,7 +167,7 @@ class LambdaFunction(BaseModel):
|
|||||||
'VpcConfig', {'SubnetIds': [], 'SecurityGroupIds': []})
|
'VpcConfig', {'SubnetIds': [], 'SecurityGroupIds': []})
|
||||||
|
|
||||||
# auto-generated
|
# auto-generated
|
||||||
self.version = '$LATEST'
|
self.version = version
|
||||||
self.last_modified = datetime.datetime.utcnow().strftime(
|
self.last_modified = datetime.datetime.utcnow().strftime(
|
||||||
'%Y-%m-%d %H:%M:%S')
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
@ -203,11 +209,15 @@ class LambdaFunction(BaseModel):
|
|||||||
self.code_size = key.size
|
self.code_size = key.size
|
||||||
self.code_sha_256 = hashlib.sha256(key.value).hexdigest()
|
self.code_sha_256 = hashlib.sha256(key.value).hexdigest()
|
||||||
|
|
||||||
self.function_arn = 'arn:aws:lambda:{}:123456789012:function:{}'.format(
|
self.function_arn = make_function_arn(self.region, ACCOUNT_ID, self.function_name, version)
|
||||||
self.region, self.function_name)
|
|
||||||
|
|
||||||
self.tags = dict()
|
self.tags = dict()
|
||||||
|
|
||||||
|
def set_version(self, version):
|
||||||
|
self.function_arn = make_function_arn(self.region, ACCOUNT_ID, self.function_name, version)
|
||||||
|
self.version = version
|
||||||
|
self.last_modified = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vpc_config(self):
|
def vpc_config(self):
|
||||||
config = self._vpc_config.copy()
|
config = self._vpc_config.copy()
|
||||||
@ -231,7 +241,7 @@ class LambdaFunction(BaseModel):
|
|||||||
"Role": self.role,
|
"Role": self.role,
|
||||||
"Runtime": self.run_time,
|
"Runtime": self.run_time,
|
||||||
"Timeout": self.timeout,
|
"Timeout": self.timeout,
|
||||||
"Version": self.version,
|
"Version": str(self.version),
|
||||||
"VpcConfig": self.vpc_config,
|
"VpcConfig": self.vpc_config,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,8 +399,7 @@ class LambdaFunction(BaseModel):
|
|||||||
from moto.cloudformation.exceptions import \
|
from moto.cloudformation.exceptions import \
|
||||||
UnformattedGetAttTemplateException
|
UnformattedGetAttTemplateException
|
||||||
if attribute_name == 'Arn':
|
if attribute_name == 'Arn':
|
||||||
return 'arn:aws:lambda:{0}:123456789012:function:{1}'.format(
|
return make_function_arn(self.region, ACCOUNT_ID, self.function_name)
|
||||||
self.region, self.function_name)
|
|
||||||
raise UnformattedGetAttTemplateException()
|
raise UnformattedGetAttTemplateException()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -446,9 +455,121 @@ class LambdaVersion(BaseModel):
|
|||||||
return LambdaVersion(spec)
|
return LambdaVersion(spec)
|
||||||
|
|
||||||
|
|
||||||
|
class LambdaStorage(object):
|
||||||
|
def __init__(self):
|
||||||
|
# Format 'func_name' {'alias': {}, 'versions': []}
|
||||||
|
self._functions = {}
|
||||||
|
self._arns = weakref.WeakValueDictionary()
|
||||||
|
|
||||||
|
def _get_latest(self, name):
|
||||||
|
return self._functions[name]['latest']
|
||||||
|
|
||||||
|
def _get_version(self, name, version):
|
||||||
|
index = version - 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._functions[name]['versions'][index]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_alias(self, name, alias):
|
||||||
|
return self._functions[name]['alias'].get(alias, None)
|
||||||
|
|
||||||
|
def get_function(self, name, qualifier=None):
|
||||||
|
if name not in self._functions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if qualifier is None:
|
||||||
|
return self._get_latest(name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._get_version(name, int(qualifier))
|
||||||
|
except ValueError:
|
||||||
|
return self._functions[name]['latest']
|
||||||
|
|
||||||
|
def get_arn(self, arn):
|
||||||
|
return self._arns.get(arn, None)
|
||||||
|
|
||||||
|
def put_function(self, fn):
|
||||||
|
"""
|
||||||
|
:param fn: Function
|
||||||
|
:type fn: LambdaFunction
|
||||||
|
"""
|
||||||
|
if fn.function_name in self._functions:
|
||||||
|
self._functions[fn.function_name]['latest'] = fn
|
||||||
|
else:
|
||||||
|
self._functions[fn.function_name] = {
|
||||||
|
'latest': fn,
|
||||||
|
'versions': [],
|
||||||
|
'alias': weakref.WeakValueDictionary()
|
||||||
|
}
|
||||||
|
|
||||||
|
self._arns[fn.function_arn] = fn
|
||||||
|
|
||||||
|
def publish_function(self, name):
|
||||||
|
if name not in self._functions:
|
||||||
|
return None
|
||||||
|
if not self._functions[name]['latest']:
|
||||||
|
return None
|
||||||
|
|
||||||
|
new_version = len(self._functions[name]['versions']) + 1
|
||||||
|
fn = copy.copy(self._functions[name]['latest'])
|
||||||
|
fn.set_version(new_version)
|
||||||
|
|
||||||
|
self._functions[name]['versions'].append(fn)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
def del_function(self, name, qualifier=None):
|
||||||
|
if name in self._functions:
|
||||||
|
if not qualifier:
|
||||||
|
# Something is still reffing this so delete all arns
|
||||||
|
latest = self._functions[name]['latest'].function_arn
|
||||||
|
del self._arns[latest]
|
||||||
|
|
||||||
|
for fn in self._functions[name]['versions']:
|
||||||
|
del self._arns[fn.function_arn]
|
||||||
|
|
||||||
|
del self._functions[name]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif qualifier == '$LATEST':
|
||||||
|
self._functions[name]['latest'] = None
|
||||||
|
|
||||||
|
# If theres no functions left
|
||||||
|
if not self._functions[name]['versions'] and not self._functions[name]['latest']:
|
||||||
|
del self._functions[name]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
fn = self.get_function(name, qualifier)
|
||||||
|
if fn:
|
||||||
|
self._functions[name]['versions'].remove(fn)
|
||||||
|
|
||||||
|
# If theres no functions left
|
||||||
|
if not self._functions[name]['versions'] and not self._functions[name]['latest']:
|
||||||
|
del self._functions[name]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for function_group in self._functions.values():
|
||||||
|
if function_group['latest'] is not None:
|
||||||
|
result.append(function_group['latest'])
|
||||||
|
|
||||||
|
result.extend(function_group['versions'])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class LambdaBackend(BaseBackend):
|
class LambdaBackend(BaseBackend):
|
||||||
def __init__(self, region_name):
|
def __init__(self, region_name):
|
||||||
self._functions = {}
|
self._lambdas = LambdaStorage()
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
@ -456,31 +577,31 @@ class LambdaBackend(BaseBackend):
|
|||||||
self.__dict__ = {}
|
self.__dict__ = {}
|
||||||
self.__init__(region_name)
|
self.__init__(region_name)
|
||||||
|
|
||||||
def has_function(self, function_name):
|
|
||||||
return function_name in self._functions
|
|
||||||
|
|
||||||
def has_function_arn(self, function_arn):
|
|
||||||
return self.get_function_by_arn(function_arn) is not None
|
|
||||||
|
|
||||||
def create_function(self, spec):
|
def create_function(self, spec):
|
||||||
fn = LambdaFunction(spec, self.region_name)
|
function_name = spec.get('FunctionName', None)
|
||||||
self._functions[fn.function_name] = fn
|
if function_name is None:
|
||||||
|
raise RESTError('InvalidParameterValueException', 'Missing FunctionName')
|
||||||
|
|
||||||
|
fn = LambdaFunction(spec, self.region_name, version='$LATEST')
|
||||||
|
|
||||||
|
self._lambdas.put_function(fn)
|
||||||
|
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
def get_function(self, function_name):
|
def publish_function(self, function_name):
|
||||||
return self._functions[function_name]
|
return self._lambdas.publish_function(function_name)
|
||||||
|
|
||||||
|
def get_function(self, function_name, qualifier=None):
|
||||||
|
return self._lambdas.get_function(function_name, qualifier)
|
||||||
|
|
||||||
def get_function_by_arn(self, function_arn):
|
def get_function_by_arn(self, function_arn):
|
||||||
for function in self._functions.values():
|
return self._lambdas.get_arn(function_arn)
|
||||||
if function.function_arn == function_arn:
|
|
||||||
return function
|
|
||||||
return None
|
|
||||||
|
|
||||||
def delete_function(self, function_name):
|
def delete_function(self, function_name, qualifier=None):
|
||||||
del self._functions[function_name]
|
return self._lambdas.del_function(function_name, qualifier)
|
||||||
|
|
||||||
def list_functions(self):
|
def list_functions(self):
|
||||||
return self._functions.values()
|
return self._lambdas.all()
|
||||||
|
|
||||||
def send_message(self, function_name, message):
|
def send_message(self, function_name, message):
|
||||||
event = {
|
event = {
|
||||||
@ -515,23 +636,31 @@ class LambdaBackend(BaseBackend):
|
|||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
self._functions[function_name].invoke(json.dumps(event), {}, {})
|
self._functions[function_name][-1].invoke(json.dumps(event), {}, {})
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def list_tags(self, resource):
|
def list_tags(self, resource):
|
||||||
return self.get_function_by_arn(resource).tags
|
return self.get_function_by_arn(resource).tags
|
||||||
|
|
||||||
def tag_resource(self, resource, tags):
|
def tag_resource(self, resource, tags):
|
||||||
self.get_function_by_arn(resource).tags.update(tags)
|
fn = self.get_function_by_arn(resource)
|
||||||
|
if not fn:
|
||||||
|
return False
|
||||||
|
|
||||||
|
fn.tags.update(tags)
|
||||||
|
return True
|
||||||
|
|
||||||
def untag_resource(self, resource, tagKeys):
|
def untag_resource(self, resource, tagKeys):
|
||||||
function = self.get_function_by_arn(resource)
|
fn = self.get_function_by_arn(resource)
|
||||||
for key in tagKeys:
|
if fn:
|
||||||
try:
|
for key in tagKeys:
|
||||||
del function.tags[key]
|
try:
|
||||||
except KeyError:
|
del fn.tags[key]
|
||||||
pass
|
except KeyError:
|
||||||
# Don't care
|
pass
|
||||||
|
# Don't care
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def add_policy(self, function_name, policy):
|
def add_policy(self, function_name, policy):
|
||||||
self.get_function(function_name).policy = policy
|
self.get_function(function_name).policy = policy
|
||||||
|
@ -5,15 +5,31 @@ import re
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from urlparse import urlparse, parse_qs
|
|
||||||
except:
|
except:
|
||||||
from urllib.parse import unquote, urlparse, parse_qs
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from moto.core.utils import amz_crc32, amzn_request_id
|
from moto.core.utils import amz_crc32, amzn_request_id
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
|
from .models import lambda_backends
|
||||||
|
|
||||||
|
|
||||||
class LambdaResponse(BaseResponse):
|
class LambdaResponse(BaseResponse):
|
||||||
|
@property
|
||||||
|
def json_body(self):
|
||||||
|
"""
|
||||||
|
:return: JSON
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return json.loads(self.body)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lambda_backend(self):
|
||||||
|
"""
|
||||||
|
Get backend
|
||||||
|
:return: Lambda Backend
|
||||||
|
:rtype: moto.awslambda.models.LambdaBackend
|
||||||
|
"""
|
||||||
|
return lambda_backends[self.region]
|
||||||
|
|
||||||
def root(self, request, full_url, headers):
|
def root(self, request, full_url, headers):
|
||||||
self.setup_class(request, full_url, headers)
|
self.setup_class(request, full_url, headers)
|
||||||
@ -33,6 +49,16 @@ class LambdaResponse(BaseResponse):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Cannot handle request")
|
raise ValueError("Cannot handle request")
|
||||||
|
|
||||||
|
def versions(self, request, full_url, headers):
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
if request.method == 'GET':
|
||||||
|
# This is ListVersionByFunction
|
||||||
|
raise ValueError("Cannot handle request")
|
||||||
|
elif request.method == 'POST':
|
||||||
|
return self._publish_function(request, full_url, headers)
|
||||||
|
else:
|
||||||
|
raise ValueError("Cannot handle request")
|
||||||
|
|
||||||
@amz_crc32
|
@amz_crc32
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def invoke(self, request, full_url, headers):
|
def invoke(self, request, full_url, headers):
|
||||||
@ -93,13 +119,12 @@ class LambdaResponse(BaseResponse):
|
|||||||
|
|
||||||
def _invoke(self, request, full_url):
|
def _invoke(self, request, full_url):
|
||||||
response_headers = {}
|
response_headers = {}
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
function_name = self.path.rsplit('/', 2)[-2]
|
||||||
function_name = path.split('/')[-2]
|
qualifier = self._get_param('qualifier')
|
||||||
|
|
||||||
if lambda_backend.has_function(function_name):
|
fn = self.lambda_backend.get_function(function_name, qualifier)
|
||||||
fn = lambda_backend.get_function(function_name)
|
if fn:
|
||||||
payload = fn.invoke(self.body, self.headers, response_headers)
|
payload = fn.invoke(self.body, self.headers, response_headers)
|
||||||
response_headers['Content-Length'] = str(len(payload))
|
response_headers['Content-Length'] = str(len(payload))
|
||||||
return 202, response_headers, payload
|
return 202, response_headers, payload
|
||||||
@ -108,66 +133,70 @@ class LambdaResponse(BaseResponse):
|
|||||||
|
|
||||||
def _invoke_async(self, request, full_url):
|
def _invoke_async(self, request, full_url):
|
||||||
response_headers = {}
|
response_headers = {}
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
function_name = self.path.rsplit('/', 3)[-3]
|
||||||
function_name = path.split('/')[-3]
|
|
||||||
if lambda_backend.has_function(function_name):
|
fn = self.lambda_backend.get_function(function_name, None)
|
||||||
fn = lambda_backend.get_function(function_name)
|
if fn:
|
||||||
fn.invoke(self.body, self.headers, response_headers)
|
payload = fn.invoke(self.body, self.headers, response_headers)
|
||||||
response_headers['Content-Length'] = str(0)
|
response_headers['Content-Length'] = str(len(payload))
|
||||||
return 202, response_headers, ""
|
return 202, response_headers, payload
|
||||||
else:
|
else:
|
||||||
return 404, response_headers, "{}"
|
return 404, response_headers, "{}"
|
||||||
|
|
||||||
def _list_functions(self, request, full_url, headers):
|
def _list_functions(self, request, full_url, headers):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
result = {
|
||||||
return 200, {}, json.dumps({
|
'Functions': []
|
||||||
"Functions": [fn.get_configuration() for fn in lambda_backend.list_functions()],
|
}
|
||||||
# "NextMarker": str(uuid.uuid4()),
|
|
||||||
})
|
for fn in self.lambda_backend.list_functions():
|
||||||
|
json_data = fn.get_configuration()
|
||||||
|
|
||||||
|
result['Functions'].append(json_data)
|
||||||
|
|
||||||
|
return 200, {}, json.dumps(result)
|
||||||
|
|
||||||
def _create_function(self, request, full_url, headers):
|
def _create_function(self, request, full_url, headers):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
|
||||||
spec = json.loads(self.body)
|
|
||||||
try:
|
try:
|
||||||
fn = lambda_backend.create_function(spec)
|
fn = self.lambda_backend.create_function(self.json_body)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {}, json.dumps({"Error": {"Code": e.args[0], "Message": e.args[1]}})
|
return 400, {}, json.dumps({"Error": {"Code": e.args[0], "Message": e.args[1]}})
|
||||||
else:
|
else:
|
||||||
config = fn.get_configuration()
|
config = fn.get_configuration()
|
||||||
return 201, {}, json.dumps(config)
|
return 201, {}, json.dumps(config)
|
||||||
|
|
||||||
|
def _publish_function(self, request, full_url, headers):
|
||||||
|
function_name = self.path.rsplit('/', 2)[-2]
|
||||||
|
|
||||||
|
fn = self.lambda_backend.publish_function(function_name)
|
||||||
|
if fn:
|
||||||
|
config = fn.get_configuration()
|
||||||
|
return 200, {}, json.dumps(config)
|
||||||
|
else:
|
||||||
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _delete_function(self, request, full_url, headers):
|
def _delete_function(self, request, full_url, headers):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
function_name = self.path.rsplit('/', 1)[-1]
|
||||||
|
qualifier = self._get_param('Qualifier', None)
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
if self.lambda_backend.delete_function(function_name, qualifier):
|
||||||
function_name = path.split('/')[-1]
|
|
||||||
|
|
||||||
if lambda_backend.has_function(function_name):
|
|
||||||
lambda_backend.delete_function(function_name)
|
|
||||||
return 204, {}, ""
|
return 204, {}, ""
|
||||||
else:
|
else:
|
||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _get_function(self, request, full_url, headers):
|
def _get_function(self, request, full_url, headers):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
function_name = self.path.rsplit('/', 1)[-1]
|
||||||
|
qualifier = self._get_param('Qualifier', None)
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
fn = self.lambda_backend.get_function(function_name, qualifier)
|
||||||
function_name = path.split('/')[-1]
|
|
||||||
|
|
||||||
if lambda_backend.has_function(function_name):
|
if fn:
|
||||||
fn = lambda_backend.get_function(function_name)
|
|
||||||
code = fn.get_code()
|
code = fn.get_code()
|
||||||
|
|
||||||
return 200, {}, json.dumps(code)
|
return 200, {}, json.dumps(code)
|
||||||
else:
|
else:
|
||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def get_lambda_backend(self, full_url):
|
|
||||||
from moto.awslambda.models import lambda_backends
|
|
||||||
region = self._get_aws_region(full_url)
|
|
||||||
return lambda_backends[region]
|
|
||||||
|
|
||||||
def _get_aws_region(self, full_url):
|
def _get_aws_region(self, full_url):
|
||||||
region = re.search(self.region_regex, full_url)
|
region = re.search(self.region_regex, full_url)
|
||||||
if region:
|
if region:
|
||||||
@ -176,41 +205,27 @@ class LambdaResponse(BaseResponse):
|
|||||||
return self.default_region
|
return self.default_region
|
||||||
|
|
||||||
def _list_tags(self, request, full_url):
|
def _list_tags(self, request, full_url):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
function_arn = unquote(self.path.rsplit('/', 1)[-1])
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
fn = self.lambda_backend.get_function_by_arn(function_arn)
|
||||||
function_arn = unquote(path.split('/')[-1])
|
if fn:
|
||||||
|
return 200, {}, json.dumps({'Tags': fn.tags})
|
||||||
if lambda_backend.has_function_arn(function_arn):
|
|
||||||
function = lambda_backend.get_function_by_arn(function_arn)
|
|
||||||
return 200, {}, json.dumps(dict(Tags=function.tags))
|
|
||||||
else:
|
else:
|
||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _tag_resource(self, request, full_url):
|
def _tag_resource(self, request, full_url):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
function_arn = unquote(self.path.rsplit('/', 1)[-1])
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
if self.lambda_backend.tag_resource(function_arn, self.json_body['Tags']):
|
||||||
function_arn = unquote(path.split('/')[-1])
|
|
||||||
|
|
||||||
spec = json.loads(self.body)
|
|
||||||
|
|
||||||
if lambda_backend.has_function_arn(function_arn):
|
|
||||||
lambda_backend.tag_resource(function_arn, spec['Tags'])
|
|
||||||
return 200, {}, "{}"
|
return 200, {}, "{}"
|
||||||
else:
|
else:
|
||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _untag_resource(self, request, full_url):
|
def _untag_resource(self, request, full_url):
|
||||||
lambda_backend = self.get_lambda_backend(full_url)
|
function_arn = unquote(self.path.rsplit('/', 1)[-1])
|
||||||
|
tag_keys = self.querystring['tagKeys']
|
||||||
|
|
||||||
path = request.path if hasattr(request, 'path') else request.path_url
|
if self.lambda_backend.untag_resource(function_arn, tag_keys):
|
||||||
function_arn = unquote(path.split('/')[-1].split('?')[0])
|
|
||||||
|
|
||||||
tag_keys = parse_qs(urlparse(full_url).query)['tagKeys']
|
|
||||||
|
|
||||||
if lambda_backend.has_function_arn(function_arn):
|
|
||||||
lambda_backend.untag_resource(function_arn, tag_keys)
|
|
||||||
return 204, {}, "{}"
|
return 204, {}, "{}"
|
||||||
else:
|
else:
|
||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
@ -10,6 +10,7 @@ response = LambdaResponse()
|
|||||||
url_paths = {
|
url_paths = {
|
||||||
'{0}/(?P<api_version>[^/]+)/functions/?$': response.root,
|
'{0}/(?P<api_version>[^/]+)/functions/?$': response.root,
|
||||||
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/?$': response.function,
|
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/?$': response.function,
|
||||||
|
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/versions/?$': response.versions,
|
||||||
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invocations/?$': response.invoke,
|
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invocations/?$': response.invoke,
|
||||||
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invoke-async/?$': response.invoke_async,
|
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invoke-async/?$': response.invoke_async,
|
||||||
r'{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)': response.tag,
|
r'{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)': response.tag,
|
||||||
|
15
moto/awslambda/utils.py
Normal file
15
moto/awslambda/utils.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
ARN = namedtuple('ARN', ['region', 'account', 'function_name', 'version'])
|
||||||
|
|
||||||
|
|
||||||
|
def make_function_arn(region, account, name, version='1'):
|
||||||
|
return 'arn:aws:lambda:{0}:{1}:function:{2}:{3}'.format(region, account, name, version)
|
||||||
|
|
||||||
|
|
||||||
|
def split_function_arn(arn):
|
||||||
|
arn = arn.replace('arn:aws:lambda:')
|
||||||
|
|
||||||
|
region, account, _, name, version = arn.split(':')
|
||||||
|
|
||||||
|
return ARN(region, account, name, version)
|
@ -106,7 +106,7 @@ class BaseResponse(_TemplateEnvironmentMixin):
|
|||||||
|
|
||||||
default_region = 'us-east-1'
|
default_region = 'us-east-1'
|
||||||
# to extract region, use [^.]
|
# to extract region, use [^.]
|
||||||
region_regex = r'\.([^.]+?)\.amazonaws\.com'
|
region_regex = r'\.(?P<region>[a-z]{2}-[a-z]+-\d{1})\.amazonaws\.com'
|
||||||
aws_service_spec = None
|
aws_service_spec = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -12,7 +12,7 @@ import sure # noqa
|
|||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
from moto import mock_lambda, mock_s3, mock_ec2, settings
|
from moto import mock_lambda, mock_s3, mock_ec2, settings
|
||||||
|
|
||||||
_lambda_region = 'us-east-1' if settings.TEST_SERVER_MODE else 'us-west-2'
|
_lambda_region = 'us-west-2'
|
||||||
|
|
||||||
|
|
||||||
def _process_lambda(func_str):
|
def _process_lambda(func_str):
|
||||||
@ -220,7 +220,7 @@ def test_create_function_from_aws_bucket():
|
|||||||
result.pop('LastModified')
|
result.pop('LastModified')
|
||||||
result.should.equal({
|
result.should.equal({
|
||||||
'FunctionName': 'testFunction',
|
'FunctionName': 'testFunction',
|
||||||
'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region),
|
'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction:$LATEST'.format(_lambda_region),
|
||||||
'Runtime': 'python2.7',
|
'Runtime': 'python2.7',
|
||||||
'Role': 'test-iam-role',
|
'Role': 'test-iam-role',
|
||||||
'Handler': 'lambda_function.lambda_handler',
|
'Handler': 'lambda_function.lambda_handler',
|
||||||
@ -265,7 +265,7 @@ def test_create_function_from_zipfile():
|
|||||||
|
|
||||||
result.should.equal({
|
result.should.equal({
|
||||||
'FunctionName': 'testFunction',
|
'FunctionName': 'testFunction',
|
||||||
'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region),
|
'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction:$LATEST'.format(_lambda_region),
|
||||||
'Runtime': 'python2.7',
|
'Runtime': 'python2.7',
|
||||||
'Role': 'test-iam-role',
|
'Role': 'test-iam-role',
|
||||||
'Handler': 'lambda_function.lambda_handler',
|
'Handler': 'lambda_function.lambda_handler',
|
||||||
@ -317,30 +317,25 @@ def test_get_function():
|
|||||||
result['ResponseMetadata'].pop('RetryAttempts', None)
|
result['ResponseMetadata'].pop('RetryAttempts', None)
|
||||||
result['Configuration'].pop('LastModified')
|
result['Configuration'].pop('LastModified')
|
||||||
|
|
||||||
result.should.equal({
|
result['Code']['Location'].should.equal('s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/test.zip'.format(_lambda_region))
|
||||||
"Code": {
|
result['Code']['RepositoryType'].should.equal('S3')
|
||||||
"Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/test.zip".format(_lambda_region),
|
|
||||||
"RepositoryType": "S3"
|
result['Configuration']['CodeSha256'].should.equal(hashlib.sha256(zip_content).hexdigest())
|
||||||
},
|
result['Configuration']['CodeSize'].should.equal(len(zip_content))
|
||||||
"Configuration": {
|
result['Configuration']['Description'].should.equal('test lambda function')
|
||||||
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
|
result['Configuration'].should.contain('FunctionArn')
|
||||||
"CodeSize": len(zip_content),
|
result['Configuration']['FunctionName'].should.equal('testFunction')
|
||||||
"Description": "test lambda function",
|
result['Configuration']['Handler'].should.equal('lambda_function.lambda_handler')
|
||||||
"FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region),
|
result['Configuration']['MemorySize'].should.equal(128)
|
||||||
"FunctionName": "testFunction",
|
result['Configuration']['Role'].should.equal('test-iam-role')
|
||||||
"Handler": "lambda_function.lambda_handler",
|
result['Configuration']['Runtime'].should.equal('python2.7')
|
||||||
"MemorySize": 128,
|
result['Configuration']['Timeout'].should.equal(3)
|
||||||
"Role": "test-iam-role",
|
result['Configuration']['Version'].should.equal('$LATEST')
|
||||||
"Runtime": "python2.7",
|
result['Configuration'].should.contain('VpcConfig')
|
||||||
"Timeout": 3,
|
|
||||||
"Version": '$LATEST',
|
# Test get function with
|
||||||
"VpcConfig": {
|
result = conn.get_function(FunctionName='testFunction', Qualifier='$LATEST')
|
||||||
"SecurityGroupIds": [],
|
result['Configuration']['Version'].should.equal('$LATEST')
|
||||||
"SubnetIds": [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'ResponseMetadata': {'HTTPStatusCode': 200},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@mock_lambda
|
@mock_lambda
|
||||||
@ -380,6 +375,52 @@ def test_delete_function():
|
|||||||
FunctionName='testFunctionThatDoesntExist').should.throw(botocore.client.ClientError)
|
FunctionName='testFunctionThatDoesntExist').should.throw(botocore.client.ClientError)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lambda
|
||||||
|
@mock_s3
|
||||||
|
def test_publish():
|
||||||
|
s3_conn = boto3.client('s3', 'us-west-2')
|
||||||
|
s3_conn.create_bucket(Bucket='test-bucket')
|
||||||
|
|
||||||
|
zip_content = get_test_zip_file2()
|
||||||
|
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
|
||||||
|
conn = boto3.client('lambda', 'us-west-2')
|
||||||
|
|
||||||
|
conn.create_function(
|
||||||
|
FunctionName='testFunction',
|
||||||
|
Runtime='python2.7',
|
||||||
|
Role='test-iam-role',
|
||||||
|
Handler='lambda_function.lambda_handler',
|
||||||
|
Code={
|
||||||
|
'S3Bucket': 'test-bucket',
|
||||||
|
'S3Key': 'test.zip',
|
||||||
|
},
|
||||||
|
Description='test lambda function',
|
||||||
|
Timeout=3,
|
||||||
|
MemorySize=128,
|
||||||
|
Publish=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
function_list = conn.list_functions()
|
||||||
|
function_list['Functions'].should.have.length_of(1)
|
||||||
|
latest_arn = function_list['Functions'][0]['FunctionArn']
|
||||||
|
|
||||||
|
conn.publish_version(FunctionName='testFunction')
|
||||||
|
|
||||||
|
function_list = conn.list_functions()
|
||||||
|
function_list['Functions'].should.have.length_of(2)
|
||||||
|
|
||||||
|
# #SetComprehension ;-)
|
||||||
|
published_arn = list({f['FunctionArn'] for f in function_list['Functions']} - {latest_arn})[0]
|
||||||
|
published_arn.should.contain('testFunction:1')
|
||||||
|
|
||||||
|
conn.delete_function(FunctionName='testFunction', Qualifier='1')
|
||||||
|
|
||||||
|
function_list = conn.list_functions()
|
||||||
|
function_list['Functions'].should.have.length_of(1)
|
||||||
|
function_list['Functions'][0]['FunctionArn'].should.contain('testFunction:$LATEST')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@mock_lambda
|
@mock_lambda
|
||||||
@mock_s3
|
@mock_s3
|
||||||
@freeze_time('2015-01-01 00:00:00')
|
@freeze_time('2015-01-01 00:00:00')
|
||||||
@ -420,7 +461,7 @@ def test_list_create_list_get_delete_list():
|
|||||||
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
|
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
|
||||||
"CodeSize": len(zip_content),
|
"CodeSize": len(zip_content),
|
||||||
"Description": "test lambda function",
|
"Description": "test lambda function",
|
||||||
"FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region),
|
"FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction:$LATEST'.format(_lambda_region),
|
||||||
"FunctionName": "testFunction",
|
"FunctionName": "testFunction",
|
||||||
"Handler": "lambda_function.lambda_handler",
|
"Handler": "lambda_function.lambda_handler",
|
||||||
"MemorySize": 128,
|
"MemorySize": 128,
|
||||||
@ -633,7 +674,7 @@ def test_get_function_created_with_zipfile():
|
|||||||
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
|
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
|
||||||
"CodeSize": len(zip_content),
|
"CodeSize": len(zip_content),
|
||||||
"Description": "test lambda function",
|
"Description": "test lambda function",
|
||||||
"FunctionArn":'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region),
|
"FunctionArn":'arn:aws:lambda:{}:123456789012:function:testFunction:$LATEST'.format(_lambda_region),
|
||||||
"FunctionName": "testFunction",
|
"FunctionName": "testFunction",
|
||||||
"Handler": "lambda_function.handler",
|
"Handler": "lambda_function.handler",
|
||||||
"MemorySize": 128,
|
"MemorySize": 128,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user