Improvements: AWSLambda (#4943)

This commit is contained in:
Bert Blommers 2022-03-17 11:32:31 -01:00 committed by GitHub
parent bbd4b2afc3
commit 96c9391beb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1089 additions and 380 deletions

View File

@ -3423,25 +3423,25 @@
## lambda
<details>
<summary>43% implemented</summary>
<summary>53% implemented</summary>
- [ ] add_layer_version_permission
- [X] add_permission
- [ ] create_alias
- [X] create_alias
- [ ] create_code_signing_config
- [X] create_event_source_mapping
- [X] create_function
- [ ] delete_alias
- [X] delete_alias
- [ ] delete_code_signing_config
- [X] delete_event_source_mapping
- [X] delete_function
- [ ] delete_function_code_signing_config
- [X] delete_function_concurrency
- [ ] delete_function_event_invoke_config
- [ ] delete_layer_version
- [X] delete_layer_version
- [ ] delete_provisioned_concurrency_config
- [ ] get_account_settings
- [ ] get_alias
- [X] get_alias
- [X] get_code_signing_config
- [X] get_event_source_mapping
- [X] get_function
@ -3449,7 +3449,7 @@
- [X] get_function_concurrency
- [ ] get_function_configuration
- [ ] get_function_event_invoke_config
- [ ] get_layer_version
- [X] get_layer_version
- [ ] get_layer_version_by_arn
- [ ] get_layer_version_policy
- [X] get_policy
@ -3477,7 +3477,7 @@
- [X] remove_permission
- [X] tag_resource
- [X] untag_resource
- [ ] update_alias
- [X] update_alias
- [ ] update_code_signing_config
- [X] update_event_source_mapping
- [X] update_function_code

View File

@ -29,21 +29,21 @@ lambda
- [ ] add_layer_version_permission
- [X] add_permission
- [ ] create_alias
- [X] create_alias
- [ ] create_code_signing_config
- [X] create_event_source_mapping
- [X] create_function
- [ ] delete_alias
- [X] delete_alias
- [ ] delete_code_signing_config
- [X] delete_event_source_mapping
- [X] delete_function
- [ ] delete_function_code_signing_config
- [X] delete_function_concurrency
- [ ] delete_function_event_invoke_config
- [ ] delete_layer_version
- [X] delete_layer_version
- [ ] delete_provisioned_concurrency_config
- [ ] get_account_settings
- [ ] get_alias
- [X] get_alias
- [X] get_code_signing_config
- [X] get_event_source_mapping
- [X] get_function
@ -51,7 +51,7 @@ lambda
- [X] get_function_concurrency
- [ ] get_function_configuration
- [ ] get_function_event_invoke_config
- [ ] get_layer_version
- [X] get_layer_version
- [ ] get_layer_version_by_arn
- [ ] get_layer_version_policy
- [X] get_policy
@ -79,7 +79,11 @@ lambda
- [X] remove_permission
- [X] tag_resource
- [X] untag_resource
- [ ] update_alias
- [X] update_alias
The RevisionId parameter is not yet implemented
- [ ] update_code_signing_config
- [X] update_event_source_mapping
- [X] update_function_code

View File

@ -35,6 +35,27 @@ class PreconditionFailedException(JsonRESTError):
super().__init__("PreconditionFailedException", message)
class UnknownAliasException(LambdaClientError):
code = 404
def __init__(self, arn):
super().__init__("ResourceNotFoundException", f"Cannot find alias arn: {arn}")
class UnknownFunctionException(LambdaClientError):
code = 404
def __init__(self, arn):
super().__init__("ResourceNotFoundException", f"Function not found: {arn}")
class UnknownLayerException(LambdaClientError):
code = 404
def __init__(self):
super().__init__("ResourceNotFoundException", "Cannot find layer")
class UnknownPolicyException(LambdaClientError):
code = 404
@ -43,10 +64,3 @@ class UnknownPolicyException(LambdaClientError):
"ResourceNotFoundException",
"No policy is associated with the given resource.",
)
class UnknownFunctionException(LambdaClientError):
code = 404
def __init__(self, arn):
super().__init__("ResourceNotFoundException", f"Function not found: {arn}")

View File

@ -23,7 +23,7 @@ import weakref
import requests.exceptions
from moto.awslambda.policy import Policy
from moto.core import BaseBackend, CloudFormationModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.exceptions import RESTError
from moto.iam.models import iam_backend
from moto.iam.exceptions import IAMNotFoundException
@ -36,7 +36,9 @@ from .exceptions import (
CrossAccountNotAllowed,
InvalidRoleFormat,
InvalidParameterValueException,
UnknownLayerException,
UnknownFunctionException,
UnknownAliasException,
)
from .utils import (
make_function_arn,
@ -50,13 +52,11 @@ from moto.dynamodb import dynamodb_backends
from moto.dynamodbstreams import dynamodbstreams_backends
from moto.core import ACCOUNT_ID
from moto.utilities.docker_utilities import DockerModel, parse_image_ref
from tempfile import TemporaryDirectory
from uuid import uuid4
logger = logging.getLogger(__name__)
try:
from tempfile import TemporaryDirectory
except ImportError:
from backports.tempfile import TemporaryDirectory
docker_3 = docker.__version__[0] >= "3"
@ -110,25 +110,26 @@ class _DockerDataVolumeContext:
def __enter__(self):
# See if volume is already known
with self.__class__._lock:
self._vol_ref = self.__class__._data_vol_map[self._lambda_func.code_sha_256]
self._vol_ref = self.__class__._data_vol_map[self._lambda_func.code_digest]
self._vol_ref.refcount += 1
if self._vol_ref.refcount > 1:
return self
# See if the volume already exists
for vol in self._lambda_func.docker_client.volumes.list():
if vol.name == self._lambda_func.code_sha_256:
if vol.name == self._lambda_func.code_digest:
self._vol_ref.volume = vol
return self
# It doesn't exist so we need to create it
self._vol_ref.volume = self._lambda_func.docker_client.volumes.create(
self._lambda_func.code_sha_256
self._lambda_func.code_digest
)
if docker_3:
volumes = {self.name: {"bind": "/tmp/data", "mode": "rw"}}
else:
volumes = {self.name: "/tmp/data"}
self._lambda_func.docker_client.images.pull(
":".join(parse_image_ref("alpine"))
)
@ -164,7 +165,17 @@ def _zipfile_content(zipfile):
except Exception:
to_unzip_code = base64.b64decode(zipfile)
return to_unzip_code, len(to_unzip_code), hashlib.sha256(to_unzip_code).hexdigest()
sha_code = hashlib.sha256(to_unzip_code)
base64ed_sha = base64.b64encode(sha_code.digest()).decode("utf-8")
sha_hex_digest = sha_code.hexdigest()
return to_unzip_code, len(to_unzip_code), base64ed_sha, sha_hex_digest
def _s3_content(key):
sha_code = hashlib.sha256(key.value)
base64ed_sha = base64.b64encode(sha_code.digest()).decode("utf-8")
sha_hex_digest = sha_code.hexdigest()
return key.value, key.size, base64ed_sha, sha_hex_digest
def _validate_s3_bucket_and_key(data):
@ -228,15 +239,21 @@ class LayerVersion(CloudFormationModel):
self._layer = None
if "ZipFile" in self.content:
self.code_bytes, self.code_size, self.code_sha_256 = _zipfile_content(
self.content["ZipFile"]
)
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _zipfile_content(self.content["ZipFile"])
else:
key = _validate_s3_bucket_and_key(self.content)
if key:
self.code_bytes = key.value
self.code_size = key.size
self.code_sha_256 = hashlib.sha256(key.value).hexdigest()
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
@property
def arn(self):
@ -251,7 +268,13 @@ class LayerVersion(CloudFormationModel):
def get_layer_version(self):
return {
"Content": {
"Location": "s3://",
"CodeSha256": self.code_sha_256,
"CodeSize": self.code_size,
},
"Version": self.version,
"LayerArn": self._layer.layer_arn,
"LayerVersionArn": self.arn,
"CreatedDate": self.created_date,
"CompatibleRuntimes": self.compatible_runtimes,
@ -288,6 +311,38 @@ class LayerVersion(CloudFormationModel):
return layer_version
class LambdaAlias(BaseModel):
def __init__(
self, region, name, function_name, function_version, description, routing_config
):
self.arn = (
f"arn:aws:lambda:{region}:{ACCOUNT_ID}:function:{function_name}:{name}"
)
self.name = name
self.function_version = function_version
self.description = description
self.routing_config = routing_config
self.revision_id = str(uuid4())
def update(self, description, function_version, routing_config):
if description is not None:
self.description = description
if function_version is not None:
self.function_version = function_version
if routing_config is not None:
self.routing_config = routing_config
def to_json(self):
return {
"AliasArn": self.arn,
"Description": self.description,
"FunctionVersion": self.function_version,
"Name": self.name,
"RevisionId": self.revision_id,
"RoutingConfig": self.routing_config or None,
}
class Layer(object):
def __init__(self, name, region):
self.region = region
@ -302,6 +357,9 @@ class Layer(object):
layer_version.attach(self, self._latest_version)
self.layer_versions[str(self._latest_version)] = layer_version
def delete_version(self, layer_version):
self.layer_versions.pop(str(layer_version), None)
def to_dict(self):
return {
"LayerName": self.name,
@ -338,6 +396,7 @@ class LambdaFunction(CloudFormationModel, DockerModel):
self.signing_profile_version_arn = spec.get("SigningProfileVersionArn")
self.signing_job_arn = spec.get("SigningJobArn")
self.code_signing_config_arn = spec.get("CodeSigningConfigArn")
self.tracing_config = spec.get("TracingConfig") or {"Mode": "PassThrough"}
self.logs_group_name = "/aws/lambda/{}".format(self.function_name)
@ -351,9 +410,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
self.last_modified = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
if "ZipFile" in self.code:
self.code_bytes, self.code_size, self.code_sha_256 = _zipfile_content(
self.code["ZipFile"]
)
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _zipfile_content(self.code["ZipFile"])
# TODO: we should be putting this in a lambda bucket
self.code["UUID"] = str(uuid.uuid4())
@ -361,9 +423,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
else:
key = _validate_s3_bucket_and_key(self.code)
if key:
self.code_bytes = key.value
self.code_size = key.size
self.code_sha_256 = hashlib.sha256(key.value).hexdigest()
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
else:
self.code_bytes = ""
self.code_size = 0
@ -378,6 +443,8 @@ class LambdaFunction(CloudFormationModel, DockerModel):
else:
self.tags = dict()
self._aliases = dict()
def set_version(self, version):
self.function_arn = make_function_ver_arn(
self.region, ACCOUNT_ID, self.function_name, version
@ -420,7 +487,7 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"FunctionName": self.function_name,
}
def get_configuration(self):
def get_configuration(self, on_create=False):
config = {
"CodeSha256": self.code_sha_256,
"CodeSize": self.code_size,
@ -440,7 +507,11 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"Layers": self.layers,
"SigningProfileVersionArn": self.signing_profile_version_arn,
"SigningJobArn": self.signing_job_arn,
"TracingConfig": self.tracing_config,
}
if not on_create:
# Only return this variable after the first creation
config["LastUpdateStatus"] = "Successful"
if self.environment_vars:
config["Environment"] = {"Variables": self.environment_vars}
@ -456,6 +527,8 @@ class LambdaFunction(CloudFormationModel, DockerModel):
},
"Configuration": self.get_configuration(),
}
if self.tags:
code["Tags"] = self.tags
if self.reserved_concurrency:
code.update(
{
@ -496,19 +569,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
if "ZipFile" in updated_spec:
self.code["ZipFile"] = updated_spec["ZipFile"]
# using the "hackery" from __init__ because it seems to work
# TODOs and FIXMEs included, because they'll need to be fixed
# in both places now
try:
to_unzip_code = base64.b64decode(
bytes(updated_spec["ZipFile"], "utf-8")
)
except Exception:
to_unzip_code = base64.b64decode(updated_spec["ZipFile"])
self.code_bytes = to_unzip_code
self.code_size = len(to_unzip_code)
self.code_sha_256 = hashlib.sha256(to_unzip_code).hexdigest()
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _zipfile_content(updated_spec["ZipFile"])
# TODO: we should be putting this in a lambda bucket
self.code["UUID"] = str(uuid.uuid4())
@ -533,9 +599,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.",
)
if key:
self.code_bytes = key.value
self.code_size = key.size
self.code_sha_256 = hashlib.sha256(key.value).hexdigest()
(
self.code_bytes,
self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
self.code["S3Bucket"] = updated_spec["S3Bucket"]
self.code["S3Key"] = updated_spec["S3Key"]
@ -781,6 +850,32 @@ class LambdaFunction(CloudFormationModel, DockerModel):
def delete(self, region):
lambda_backends[region].delete_function(self.function_name)
def delete_alias(self, name):
self._aliases.pop(name, None)
def get_alias(self, name):
if name in self._aliases:
return self._aliases[name]
arn = f"arn:aws:lambda:{self.region}:{ACCOUNT_ID}:function:{self.function_name}:{name}"
raise UnknownAliasException(arn)
def put_alias(self, name, description, function_version, routing_config):
alias = LambdaAlias(
region=self.region,
name=name,
function_name=self.function_name,
function_version=function_version,
description=description,
routing_config=routing_config,
)
self._aliases[name] = alias
return alias
def update_alias(self, name, description, function_version, routing_config):
alias = self.get_alias(name)
alias.update(description, function_version, routing_config)
return alias
class EventSourceMapping(CloudFormationModel):
def __init__(self, spec):
@ -934,8 +1029,9 @@ class LambdaVersion(CloudFormationModel):
class LambdaStorage(object):
def __init__(self, region_name):
# Format 'func_name' {'alias': {}, 'versions': []}
# Format 'func_name' {'versions': []}
self._functions = {}
self._aliases = dict()
self._arns = weakref.WeakValueDictionary()
self.region_name = region_name
@ -950,8 +1046,25 @@ class LambdaStorage(object):
except IndexError:
return None
def _get_alias(self, name, alias):
return self._functions[name]["alias"].get(alias, None)
def delete_alias(self, name, function_name):
fn = self.get_function_by_name_or_arn(function_name)
return fn.delete_alias(name)
def get_alias(self, name, function_name):
fn = self.get_function_by_name_or_arn(function_name)
return fn.get_alias(name)
def put_alias(
self, name, function_name, function_version, description, routing_config
):
fn = self.get_function_by_name_or_arn(function_name)
return fn.put_alias(name, description, function_version, routing_config)
def update_alias(
self, name, function_name, function_version, description, routing_config
):
fn = self.get_function_by_name_or_arn(function_name)
return fn.update_alias(name, description, function_version, routing_config)
def get_function_by_name(self, name, qualifier=None):
if name not in self._functions:
@ -974,6 +1087,11 @@ class LambdaStorage(object):
return [latest] + self._functions[name]["versions"]
def get_arn(self, arn):
# Function ARN may contain an alias
# arn:aws:lambda:region:account_id:function:<fn_name>:<alias_name>
if ":" in arn.split(":function:")[-1]:
# arn = arn:aws:lambda:region:account_id:function:<fn_name>
arn = ":".join(arn.split(":")[0:-1])
return self._arns.get(arn, None)
def get_function_by_name_or_arn(self, name_or_arn, qualifier=None):
@ -1002,11 +1120,7 @@ class LambdaStorage(object):
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._functions[fn.function_name] = {"latest": fn, "versions": []}
# instantiate a new policy for this version of the lambda
fn.policy = Policy(fn)
self._arns[fn.function_arn] = fn
@ -1122,6 +1236,17 @@ class LayerStorage(object):
def list_layers(self):
return [layer.to_dict() for layer in self._layers.values()]
def delete_layer_version(self, layer_name, layer_version):
self._layers[layer_name].delete_version(layer_version)
def get_layer_version(self, layer_name, layer_version):
if layer_name not in self._layers:
raise UnknownLayerException()
for lv in self._layers[layer_name].layer_versions.values():
if lv.version == int(layer_version):
return lv
raise UnknownLayerException()
def get_layer_versions(self, layer_name):
if layer_name in self._layers:
return list(iter(self._layers[layer_name].layer_versions.values()))
@ -1188,7 +1313,7 @@ class LambdaBackend(BaseBackend):
"""
def __init__(self, region_name):
self._lambdas = LambdaStorage(region_name)
self._lambdas = LambdaStorage(region_name=region_name)
self._event_source_mappings = {}
self._layers = LayerStorage()
self.region_name = region_name
@ -1205,6 +1330,29 @@ class LambdaBackend(BaseBackend):
service_region, zones, "lambda"
)
def create_alias(
self, name, function_name, function_version, description, routing_config
):
return self._lambdas.put_alias(
name, function_name, function_version, description, routing_config
)
def delete_alias(self, name, function_name):
return self._lambdas.delete_alias(name, function_name)
def get_alias(self, name, function_name):
return self._lambdas.get_alias(name, function_name)
def update_alias(
self, name, function_name, function_version, description, routing_config
):
"""
The RevisionId parameter is not yet implemented
"""
return self._lambdas.update_alias(
name, function_name, function_version, description, routing_config
)
def create_function(self, spec):
function_name = spec.get("FunctionName", None)
if function_name is None:
@ -1274,9 +1422,7 @@ class LambdaBackend(BaseBackend):
required = ["LayerName", "Content"]
for param in required:
if not spec.get(param):
raise RESTError(
"InvalidParameterValueException", "Missing {}".format(param)
)
raise InvalidParameterValueException("Missing {}".format(param))
layer_version = LayerVersion(spec, self.region_name)
self._layers.put_layer_version(layer_version)
return layer_version
@ -1284,6 +1430,12 @@ class LambdaBackend(BaseBackend):
def list_layers(self):
return self._layers.list_layers()
def delete_layer_version(self, layer_name, layer_version):
return self._layers.delete_layer_version(layer_name, layer_version)
def get_layer_version(self, layer_name, layer_version):
return self._layers.get_layer_version(layer_name, layer_version)
def get_layer_versions(self, layer_name):
return self._layers.get_layer_versions(layer_name)
@ -1485,10 +1637,8 @@ class LambdaBackend(BaseBackend):
def get_policy(self, function_name):
fn = self.get_function(function_name)
return fn.policy.get_policy()
def get_policy_wire_format(self, function_name):
fn = self.get_function(function_name)
if not fn:
raise UnknownFunctionException(function_name)
return fn.policy.wire_format()
def update_function_code(self, function_name, qualifier, body):

View File

@ -47,6 +47,20 @@ class LambdaResponse(BaseResponse):
else:
raise ValueError("Cannot handle request")
def aliases(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self._create_alias()
def alias(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "DELETE":
return self._delete_alias()
elif request.method == "GET":
return self._get_alias()
elif request.method == "PUT":
return self._update_alias()
def event_source_mapping(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
path = request.path if hasattr(request, "path") else path_url(request.url)
@ -65,6 +79,13 @@ class LambdaResponse(BaseResponse):
if request.method == "GET":
return self._list_layers()
def layers_version(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "DELETE":
return self._delete_layer_version()
elif request.method == "GET":
return self._get_layer_version()
def layers_versions(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "GET":
@ -182,11 +203,8 @@ class LambdaResponse(BaseResponse):
def _get_policy(self, request):
path = request.path if hasattr(request, "path") else path_url(request.url)
function_name = unquote(path.split("/")[-2])
if self.lambda_backend.get_function(function_name):
out = self.lambda_backend.get_policy_wire_format(function_name)
return 200, {}, out
else:
return 404, {}, "{}"
out = self.lambda_backend.get_policy(function_name)
return 200, {}, out
def _del_policy(self, request, querystring):
path = request.path if hasattr(request, "path") else path_url(request.url)
@ -270,7 +288,7 @@ class LambdaResponse(BaseResponse):
def _create_function(self):
fn = self.lambda_backend.create_function(self.json_body)
config = fn.get_configuration()
config = fn.get_configuration(on_create=True)
return 201, {}, json.dumps(config)
def _create_event_source_mapping(self):
@ -466,6 +484,20 @@ class LambdaResponse(BaseResponse):
layers = self.lambda_backend.list_layers()
return 200, {}, json.dumps({"Layers": layers})
def _delete_layer_version(self):
layer_name = self.path.split("/")[-3]
layer_version = self.path.split("/")[-1]
self.lambda_backend.delete_layer_version(layer_name, layer_version)
return 200, {}, "{}"
def _get_layer_version(self):
layer_name = self.path.split("/")[-3]
layer_version = self.path.split("/")[-1]
layer = self.lambda_backend.get_layer_version(layer_name, layer_version)
return 200, {}, json.dumps(layer.get_layer_version())
def _get_layer_versions(self):
layer_name = self.path.rsplit("/", 2)[-2]
layer_versions = self.lambda_backend.get_layer_versions(layer_name)
@ -484,3 +516,49 @@ class LambdaResponse(BaseResponse):
layer_version = self.lambda_backend.publish_layer_version(spec)
config = layer_version.get_layer_version()
return 201, {}, json.dumps(config)
def _create_alias(self):
function_name = unquote(self.path.rsplit("/", 2)[-2])
params = json.loads(self.body)
alias_name = params.get("Name")
description = params.get("Description", "")
function_version = params.get("FunctionVersion")
routing_config = params.get("RoutingConfig")
alias = self.lambda_backend.create_alias(
name=alias_name,
function_name=function_name,
function_version=function_version,
description=description,
routing_config=routing_config,
)
return 201, {}, json.dumps(alias.to_json())
def _delete_alias(self):
function_name = unquote(self.path.rsplit("/")[-3])
alias_name = unquote(self.path.rsplit("/", 2)[-1])
self.lambda_backend.delete_alias(name=alias_name, function_name=function_name)
return 201, {}, "{}"
def _get_alias(self):
function_name = unquote(self.path.rsplit("/")[-3])
alias_name = unquote(self.path.rsplit("/", 2)[-1])
alias = self.lambda_backend.get_alias(
name=alias_name, function_name=function_name
)
return 201, {}, json.dumps(alias.to_json())
def _update_alias(self):
function_name = unquote(self.path.rsplit("/")[-3])
alias_name = unquote(self.path.rsplit("/", 2)[-1])
params = json.loads(self.body)
description = params.get("Description")
function_version = params.get("FunctionVersion")
routing_config = params.get("RoutingConfig")
alias = self.lambda_backend.update_alias(
name=alias_name,
function_name=function_name,
function_version=function_version,
description=description,
routing_config=routing_config,
)
return 201, {}, json.dumps(alias.to_json())

View File

@ -7,6 +7,8 @@ response = LambdaResponse()
url_paths = {
r"{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_:%-]+)/aliases$": response.aliases,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/aliases/(?P<alias_name>[\w_-]+)$": response.alias,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/versions/?$": response.versions,
r"{0}/(?P<api_version>[^/]+)/event-source-mappings/?$": response.event_source_mappings,
r"{0}/(?P<api_version>[^/]+)/event-source-mappings/(?P<UUID>[\w_-]+)/?$": response.event_source_mapping,
@ -22,4 +24,5 @@ url_paths = {
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/concurrency/?$": response.function_concurrency,
r"{0}/(?P<api_version>[^/]+)/layers/?$": response.list_layers,
r"{0}/(?P<api_version>[^/]+)/layers/(?P<layer_name>[\w_-]+)/versions/?$": response.layers_versions,
r"{0}/(?P<api_version>[^/]+)/layers/(?P<layer_name>[\w_-]+)/versions/(?P<layer_version>[\w_-]+)$": response.layers_version,
}

View File

@ -98,6 +98,8 @@ TestAccAWSIPRanges
TestAccAWSKinesisStream
TestAccAWSKmsAlias
TestAccAWSKmsSecretDataSource
TestAccAWSLambdaAlias
TestAccAWSLambdaLayerVersion
TestAccAWSMq
TestAccAWSNatGateway
TestAccAWSPartition
@ -133,5 +135,7 @@ TestAccDataSourceAWSEFSAccessPoint
TestAccDataSourceAWSEFSAccessPoints
TestAccDataSourceAwsEfsFileSystem
TestAccDataSourceAwsEfsMountTarget
TestAccDataSourceAWSLambdaLayerVersion
TestAccDataSourceAwsLambdaInvocation
TestAccDataSourceAwsNetworkInterface_
TestValidateSSMDocumentPermissions

View File

@ -1,7 +1,7 @@
import base64
import botocore.client
import boto3
import hashlib
import json
import sure # noqa # pylint: disable=unused-import
import pytest
@ -122,37 +122,19 @@ def test_create_function_from_aws_bucket():
Publish=True,
VpcConfig={"SecurityGroupIds": ["sg-123abc"], "SubnetIds": ["subnet-123abc"]},
)
# this is hard to match against, so remove it
result["ResponseMetadata"].pop("HTTPHeaders", None)
# Botocore inserts retry attempts not seen in Python27
result["ResponseMetadata"].pop("RetryAttempts", None)
result.pop("LastModified")
result.should.equal(
{
"FunctionName": function_name,
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}".format(
_lambda_region, ACCOUNT_ID, function_name
),
"Runtime": "python2.7",
"Role": result["Role"],
"Handler": "lambda_function.lambda_handler",
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSize": len(zip_content),
"Description": "test lambda function",
"Timeout": 3,
"MemorySize": 128,
"PackageType": "ZIP",
"Version": "1",
"VpcConfig": {
"SecurityGroupIds": ["sg-123abc"],
"SubnetIds": ["subnet-123abc"],
"VpcId": "vpc-123abc",
},
"ResponseMetadata": {"HTTPStatusCode": 201},
"State": "Active",
"Layers": [],
}
result.should.have.key("FunctionName").equals(function_name)
result.should.have.key("FunctionArn").equals(
"arn:aws:lambda:{}:{}:function:{}".format(
_lambda_region, ACCOUNT_ID, function_name
)
)
result.should.have.key("Runtime").equals("python2.7")
result.should.have.key("Handler").equals("lambda_function.lambda_handler")
result.should.have.key("CodeSha256").equals(
base64.b64encode(hashlib.sha256(zip_content).digest()).decode("utf-8")
)
result.should.have.key("State").equals("Active")
@mock_lambda
@ -191,16 +173,46 @@ def test_create_function_from_zipfile():
"Description": "test lambda function",
"Timeout": 3,
"MemorySize": 128,
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSha256": base64.b64encode(hashlib.sha256(zip_content).digest()).decode(
"utf-8"
),
"Version": "1",
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"ResponseMetadata": {"HTTPStatusCode": 201},
"State": "Active",
"Layers": [],
"TracingConfig": {"Mode": "PassThrough"},
}
)
@mock_lambda
@pytest.mark.parametrize(
"tracing_mode",
[(None, "PassThrough"), ("PassThrough", "PassThrough"), ("Active", "Active")],
)
def test_create_function__with_tracingmode(tracing_mode):
conn = boto3.client("lambda", _lambda_region)
source, output = tracing_mode
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
kwargs = dict(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
if source:
kwargs["TracingConfig"] = {"Mode": source}
result = conn.create_function(**kwargs)
result.should.have.key("TracingConfig").should.equal({"Mode": output})
@mock_lambda
@mock_s3
@freeze_time("2015-01-01 00:00:00")
@ -243,7 +255,7 @@ def test_get_function():
result["Code"]["RepositoryType"].should.equal("S3")
result["Configuration"]["CodeSha256"].should.equal(
hashlib.sha256(zip_content).hexdigest()
base64.b64encode(hashlib.sha256(zip_content).digest()).decode("utf-8")
)
result["Configuration"]["CodeSize"].should.equal(len(zip_content))
result["Configuration"]["Description"].should.equal("test lambda function")
@ -309,7 +321,9 @@ def test_get_function_configuration(key):
result = conn.get_function_configuration(FunctionName=name_or_arn)
result["CodeSha256"].should.equal(hashlib.sha256(zip_content).hexdigest())
result["CodeSha256"].should.equal(
base64.b64encode(hashlib.sha256(zip_content).digest()).decode("utf-8")
)
result["CodeSize"].should.equal(len(zip_content))
result["Description"].should.equal("test lambda function")
result.should.contain("FunctionArn")
@ -600,7 +614,9 @@ def test_list_create_list_get_delete_list():
"RepositoryType": "S3",
},
"Configuration": {
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSha256": base64.b64encode(hashlib.sha256(zip_content).digest()).decode(
"utf-8"
),
"CodeSize": len(zip_content),
"Description": "test lambda function",
"FunctionName": function_name,
@ -613,6 +629,8 @@ def test_list_create_list_get_delete_list():
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"State": "Active",
"Layers": [],
"LastUpdateStatus": "Successful",
"TracingConfig": {"Mode": "PassThrough"},
},
"ResponseMetadata": {"HTTPStatusCode": 200},
}
@ -673,90 +691,6 @@ def test_list_create_list_get_delete_list():
func_names.shouldnt.contain(function_name)
@mock_lambda
@mock_s3
def test_tags():
"""
test list_tags -> tag_resource -> list_tags -> tag_resource -> list_tags -> untag_resource -> list_tags integration
"""
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={"LocationConstraint": _lambda_region},
)
zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
function_name = str(uuid4())[0:6]
function = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.handler",
Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
# List tags when there are none
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(dict())
# List tags when there is one
conn.tag_resource(Resource=function["FunctionArn"], Tags=dict(spam="eggs"))[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(200)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(spam="eggs")
)
# List tags when another has been added
conn.tag_resource(Resource=function["FunctionArn"], Tags=dict(foo="bar"))[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(200)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(spam="eggs", foo="bar")
)
# Untag resource
conn.untag_resource(Resource=function["FunctionArn"], TagKeys=["spam", "trolls"])[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(204)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(foo="bar")
)
# Untag a tag that does not exist (no error and no change)
conn.untag_resource(Resource=function["FunctionArn"], TagKeys=["spam"])[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(204)
@mock_lambda
def test_tags_not_found():
"""
Test list_tags and tag_resource when the lambda with the given arn does not exist
"""
conn = boto3.client("lambda", _lambda_region)
conn.list_tags.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID)
).should.throw(botocore.client.ClientError)
conn.tag_resource.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
Tags=dict(spam="eggs"),
).should.throw(botocore.client.ClientError)
conn.untag_resource.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
TagKeys=["spam"],
).should.throw(botocore.client.ClientError)
@mock_lambda
@freeze_time("2015-01-01 00:00:00")
def test_get_function_created_with_zipfile():
@ -776,7 +710,6 @@ def test_get_function_created_with_zipfile():
)
response = conn.get_function(FunctionName=function_name)
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2
@ -784,100 +717,26 @@ def test_get_function_created_with_zipfile():
assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
)
response["Configuration"].should.equal(
{
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSize": len(zip_content),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}".format(
_lambda_region, ACCOUNT_ID, function_name
),
"FunctionName": function_name,
"Handler": "lambda_function.handler",
"MemorySize": 128,
"Role": get_role_name(),
"Runtime": "python2.7",
"Timeout": 3,
"Version": "$LATEST",
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"State": "Active",
"Layers": [],
}
response.should.have.key("Configuration")
config = response["Configuration"]
config.should.have.key("CodeSha256").equals(
base64.b64encode(hashlib.sha256(zip_content).digest()).decode("utf-8")
)
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_add_function_permission(key):
"""
Parametrized to ensure that we can add permission by using the FunctionName and the FunctionArn
"""
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
config.should.have.key("CodeSize").equals(len(zip_content))
config.should.have.key("Description").equals("test lambda function")
config.should.have.key("FunctionArn").equals(
f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}"
)
name_or_arn = f[key]
response = conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
assert "Statement" in response
res = json.loads(response["Statement"])
assert res["Action"] == "lambda:InvokeFunction"
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_get_function_policy(key):
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
name_or_arn = f[key]
conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
response = conn.get_policy(FunctionName=name_or_arn)
assert "Policy" in response
res = json.loads(response["Policy"])
assert res["Statement"][0]["Action"] == "lambda:InvokeFunction"
config.should.have.key("FunctionName").equals(function_name)
config.should.have.key("Handler").equals("lambda_function.handler")
config.should.have.key("MemorySize").equals(128)
config.should.have.key("Role").equals(get_role_name())
config.should.have.key("Runtime").equals("python2.7")
config.should.have.key("Timeout").equals(3)
config.should.have.key("Version").equals("$LATEST")
config.should.have.key("State").equals("Active")
config.should.have.key("Layers").equals([])
config.should.have.key("LastUpdateStatus").equals("Successful")
@mock_lambda
@ -1085,7 +944,6 @@ def test_update_function_zip(key):
)
response = conn.get_function(FunctionName=function_name, Qualifier="2")
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2
@ -1093,26 +951,16 @@ def test_update_function_zip(key):
assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
)
response["Configuration"].should.equal(
{
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
"CodeSize": len(zip_content_two),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}:2".format(
_lambda_region, ACCOUNT_ID, function_name
),
"FunctionName": function_name,
"Handler": "lambda_function.lambda_handler",
"MemorySize": 128,
"Role": fxn["Role"],
"Runtime": "python2.7",
"Timeout": 3,
"Version": "2",
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"State": "Active",
"Layers": [],
}
config = response["Configuration"]
config.should.have.key("CodeSize").equals(len(zip_content_two))
config.should.have.key("Description").equals("test lambda function")
config.should.have.key("FunctionArn").equals(
f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}:2"
)
config.should.have.key("FunctionName").equals(function_name)
config.should.have.key("Version").equals("2")
config.should.have.key("LastUpdateStatus").equals("Successful")
@mock_lambda
@ -1131,7 +979,7 @@ def test_update_function_s3():
conn = boto3.client("lambda", _lambda_region)
function_name = str(uuid4())[0:6]
fxn = conn.create_function(
conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
@ -1154,7 +1002,6 @@ def test_update_function_s3():
)
response = conn.get_function(FunctionName=function_name, Qualifier="2")
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2
@ -1162,26 +1009,19 @@ def test_update_function_s3():
assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
)
response["Configuration"].should.equal(
{
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
"CodeSize": len(zip_content_two),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}:2".format(
_lambda_region, ACCOUNT_ID, function_name
),
"FunctionName": function_name,
"Handler": "lambda_function.lambda_handler",
"MemorySize": 128,
"Role": fxn["Role"],
"Runtime": "python2.7",
"Timeout": 3,
"Version": "2",
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"State": "Active",
"Layers": [],
}
config = response["Configuration"]
config.should.have.key("CodeSha256").equals(
base64.b64encode(hashlib.sha256(zip_content_two).digest()).decode("utf-8")
)
config.should.have.key("CodeSize").equals(len(zip_content_two))
config.should.have.key("Description").equals("test lambda function")
config.should.have.key("FunctionArn").equals(
f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}:2"
)
config.should.have.key("FunctionName").equals(function_name)
config.should.have.key("Version").equals("2")
config.should.have.key("LastUpdateStatus").equals("Successful")
@mock_lambda
@ -1210,45 +1050,6 @@ def test_create_function_with_unknown_arn():
)
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_remove_function_permission(key):
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
name_or_arn = f[key]
conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
remove = conn.remove_permission(
FunctionName=name_or_arn, StatementId="1", Qualifier="2"
)
remove["ResponseMetadata"]["HTTPStatusCode"].should.equal(204)
policy = conn.get_policy(FunctionName=name_or_arn, Qualifier="2")["Policy"]
policy = json.loads(policy)
policy["Statement"].should.equal([])
@mock_lambda
def test_remove_unknown_permission_throws_error():
conn = boto3.client("lambda", _lambda_region)

View File

@ -0,0 +1,299 @@
"""Unit tests for lambda-supported APIs."""
import boto3
import pytest
import sure # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError
from moto import mock_lambda
from moto.core import ACCOUNT_ID
from uuid import uuid4
from .utilities import (
get_role_name,
get_test_zip_file1,
)
# See our Development Tips on writing tests for hints on how to write good tests:
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
@mock_lambda
def test_create_alias():
client = boto3.client("lambda", region_name="ap-southeast-1")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
resp = client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:ap-southeast-1:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
resp.should.have.key("Description").equals("")
resp.should.have.key("RevisionId")
resp.shouldnt.have.key("RoutingConfig")
@mock_lambda
def test_create_alias_with_routing_config():
client = boto3.client("lambda", region_name="ap-southeast-1")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
resp = client.create_alias(
FunctionName=function_name,
Name="alias1",
FunctionVersion="$LATEST",
Description="desc",
RoutingConfig={"AdditionalVersionWeights": {"2": 0.5}},
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("Description").equals("desc")
resp.should.have.key("RoutingConfig").equals(
{"AdditionalVersionWeights": {"2": 0.5}}
)
@mock_lambda
def test_create_alias_using_function_arn():
client = boto3.client("lambda", region_name="ap-southeast-1")
function_name = str(uuid4())[0:6]
fn = client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
fn_arn = fn["FunctionArn"]
resp = client.create_alias(
FunctionName=fn_arn, Name="alias1", FunctionVersion="$LATEST"
)
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:ap-southeast-1:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
@mock_lambda
def test_delete_alias():
client = boto3.client("lambda", region_name="us-east-2")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
client.delete_alias(FunctionName=function_name, Name="alias1")
with pytest.raises(ClientError) as exc:
client.get_alias(FunctionName=function_name, Name="alias1")
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
@mock_lambda
def test_get_alias():
client = boto3.client("lambda", region_name="us-west-1")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
resp = client.get_alias(FunctionName=function_name, Name="alias1")
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:us-west-1:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
resp.should.have.key("Description").equals("")
resp.should.have.key("RevisionId")
@mock_lambda
def test_get_alias_using_function_arn():
client = boto3.client("lambda", region_name="us-west-1")
function_name = str(uuid4())[0:6]
fn = client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
fn_arn = fn["FunctionArn"]
client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
resp = client.get_alias(FunctionName=fn_arn, Name="alias1")
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:us-west-1:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
resp.should.have.key("Description").equals("")
resp.should.have.key("RevisionId")
@mock_lambda
def test_get_alias_using_alias_arn():
client = boto3.client("lambda", region_name="us-west-1")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
alias = client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
alias_arn = alias["AliasArn"]
resp = client.get_alias(FunctionName=alias_arn, Name="alias1")
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:us-west-1:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
resp.should.have.key("Description").equals("")
resp.should.have.key("RevisionId")
@mock_lambda
def test_get_unknown_alias():
client = boto3.client("lambda", region_name="us-west-1")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
with pytest.raises(ClientError) as exc:
client.get_alias(FunctionName=function_name, Name="unknown")
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal(
f"Cannot find alias arn: arn:aws:lambda:us-west-1:{ACCOUNT_ID}:function:{function_name}:unknown"
)
@mock_lambda
def test_update_alias():
client = boto3.client("lambda", region_name="us-east-2")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
client.create_alias(
FunctionName=function_name, Name="alias1", FunctionVersion="$LATEST"
)
resp = client.update_alias(
FunctionName=function_name,
Name="alias1",
FunctionVersion="1",
Description="updated desc",
)
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:us-east-2:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("1")
resp.should.have.key("Description").equals("updated desc")
resp.should.have.key("RevisionId")
@mock_lambda
def test_update_alias_routingconfig():
client = boto3.client("lambda", region_name="us-east-2")
function_name = str(uuid4())[0:6]
client.create_function(
FunctionName=function_name,
Runtime="python3.7",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_test_zip_file1()},
)
client.create_alias(
FunctionName=function_name,
Name="alias1",
Description="desc",
FunctionVersion="$LATEST",
)
resp = client.update_alias(
FunctionName=function_name,
Name="alias1",
RoutingConfig={"AdditionalVersionWeights": {"2": 0.5}},
)
resp.should.have.key("AliasArn").equals(
f"arn:aws:lambda:us-east-2:{ACCOUNT_ID}:function:{function_name}:alias1"
)
resp.should.have.key("Name").equals("alias1")
resp.should.have.key("FunctionVersion").equals("$LATEST")
resp.should.have.key("Description").equals("desc")
resp.should.have.key("RoutingConfig").equals(
{"AdditionalVersionWeights": {"2": 0.5}}
)

View File

@ -5,7 +5,6 @@ import sure # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError
from freezegun import freeze_time
from moto import mock_lambda, mock_s3
from moto.core.exceptions import RESTError
from moto.sts.models import ACCOUNT_ID
from uuid import uuid4
@ -15,6 +14,23 @@ _lambda_region = "us-west-2"
boto3.setup_default_session(region_name=_lambda_region)
@mock_lambda
def test_publish_lambda_layers__without_content():
conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6]
with pytest.raises(ClientError) as exc:
conn.publish_layer_version(
LayerName=layer_name,
Content={},
CompatibleRuntimes=["python3.6"],
LicenseInfo="MIT",
)
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidParameterValueException")
err["Message"].should.equal("Missing Content")
@mock_lambda
@mock_s3
@freeze_time("2015-01-01 00:00:00")
@ -31,13 +47,6 @@ def test_get_lambda_layers():
conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6]
with pytest.raises((RESTError, ClientError)):
conn.publish_layer_version(
LayerName=layer_name,
Content={},
CompatibleRuntimes=["python3.6"],
LicenseInfo="MIT",
)
conn.publish_layer_version(
LayerName=layer_name,
Content={"ZipFile": get_test_zip_file1()},
@ -123,3 +132,93 @@ def test_get_lambda_layers():
Environment={"Variables": {"test_variable": "test_value"}},
Layers=[(expected_arn + "3")],
)
@mock_lambda
@mock_s3
def test_get_layer_version():
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={"LocationConstraint": _lambda_region},
)
zip_content = get_test_zip_file1()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6]
resp = conn.publish_layer_version(
LayerName=layer_name,
Content={"ZipFile": get_test_zip_file1()},
CompatibleRuntimes=["python3.6"],
LicenseInfo="MIT",
)
layer_version = resp["Version"]
resp = conn.get_layer_version(LayerName=layer_name, VersionNumber=layer_version)
@mock_lambda
@mock_s3
def test_get_layer_version__unknown():
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={"LocationConstraint": _lambda_region},
)
zip_content = get_test_zip_file1()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6]
# Delete Layer that never existed
with pytest.raises(ClientError) as exc:
conn.get_layer_version(LayerName=layer_name, VersionNumber=1)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
conn.publish_layer_version(
LayerName=layer_name,
Content={"ZipFile": get_test_zip_file1()},
CompatibleRuntimes=["python3.6"],
LicenseInfo="MIT",
)
# Delete Version that never existed
with pytest.raises(ClientError) as exc:
conn.get_layer_version(LayerName=layer_name, VersionNumber=999)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
@mock_lambda
@mock_s3
def test_delete_layer_version():
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={"LocationConstraint": _lambda_region},
)
zip_content = get_test_zip_file1()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6]
resp = conn.publish_layer_version(
LayerName=layer_name,
Content={"ZipFile": get_test_zip_file1()},
CompatibleRuntimes=["python3.6"],
LicenseInfo="MIT",
)
layer_version = resp["Version"]
conn.delete_layer_version(LayerName=layer_name, VersionNumber=layer_version)
result = conn.list_layer_versions(LayerName=layer_name)["LayerVersions"]
result.should.equal([])

View File

@ -0,0 +1,137 @@
import boto3
import json
import sure # noqa # pylint: disable=unused-import
import pytest
from botocore.exceptions import ClientError
from moto import mock_lambda, mock_s3
from uuid import uuid4
from .utilities import get_role_name, get_test_zip_file1
_lambda_region = "us-west-2"
boto3.setup_default_session(region_name=_lambda_region)
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_add_function_permission(key):
"""
Parametrized to ensure that we can add permission by using the FunctionName and the FunctionArn
"""
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
name_or_arn = f[key]
response = conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
assert "Statement" in response
res = json.loads(response["Statement"])
assert res["Action"] == "lambda:InvokeFunction"
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_get_function_policy(key):
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
name_or_arn = f[key]
conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
response = conn.get_policy(FunctionName=name_or_arn)
assert "Policy" in response
res = json.loads(response["Policy"])
assert res["Statement"][0]["Action"] == "lambda:InvokeFunction"
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
@mock_lambda
def test_remove_function_permission(key):
conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1()
function_name = str(uuid4())[0:6]
f = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": zip_content},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
name_or_arn = f[key]
conn.add_permission(
FunctionName=name_or_arn,
StatementId="1",
Action="lambda:InvokeFunction",
Principal="432143214321",
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount="123412341234",
EventSourceToken="blah",
Qualifier="2",
)
remove = conn.remove_permission(
FunctionName=name_or_arn, StatementId="1", Qualifier="2"
)
remove["ResponseMetadata"]["HTTPStatusCode"].should.equal(204)
policy = conn.get_policy(FunctionName=name_or_arn, Qualifier="2")["Policy"]
policy = json.loads(policy)
policy["Statement"].should.equal([])
@mock_lambda
@mock_s3
def test_get_unknown_policy():
conn = boto3.client("lambda", _lambda_region)
with pytest.raises(ClientError) as exc:
conn.get_policy(FunctionName="unknown")
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
err["Message"].should.equal("Function not found: unknown")

View File

@ -0,0 +1,120 @@
import botocore.client
import boto3
import sure # noqa # pylint: disable=unused-import
from moto import mock_lambda, mock_s3
from moto.core.models import ACCOUNT_ID
from uuid import uuid4
from .utilities import get_role_name, get_test_zip_file2
_lambda_region = "us-east-1"
boto3.setup_default_session(region_name=_lambda_region)
@mock_lambda
@mock_s3
def test_tags():
"""
test list_tags -> tag_resource -> list_tags -> tag_resource -> list_tags -> untag_resource -> list_tags integration
"""
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(Bucket=bucket_name)
zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
function_name = str(uuid4())[0:6]
function = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.handler",
Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
# List tags when there are none
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(dict())
# List tags when there is one
conn.tag_resource(Resource=function["FunctionArn"], Tags=dict(spam="eggs"))[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(200)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(spam="eggs")
)
# List tags when another has been added
conn.tag_resource(Resource=function["FunctionArn"], Tags=dict(foo="bar"))[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(200)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(spam="eggs", foo="bar")
)
# Untag resource
conn.untag_resource(Resource=function["FunctionArn"], TagKeys=["spam", "trolls"])[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(204)
conn.list_tags(Resource=function["FunctionArn"])["Tags"].should.equal(
dict(foo="bar")
)
# Untag a tag that does not exist (no error and no change)
conn.untag_resource(Resource=function["FunctionArn"], TagKeys=["spam"])[
"ResponseMetadata"
]["HTTPStatusCode"].should.equal(204)
@mock_lambda
@mock_s3
def test_create_function_with_tags():
bucket_name = str(uuid4())
s3_conn = boto3.client("s3", _lambda_region)
s3_conn.create_bucket(Bucket=bucket_name)
zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
conn = boto3.client("lambda", _lambda_region)
function_name = str(uuid4())[0:6]
function = conn.create_function(
FunctionName=function_name,
Runtime="python2.7",
Role=get_role_name(),
Handler="lambda_function.handler",
Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
Tags={"key1": "val1", "key2": "val2"},
)
tags = conn.list_tags(Resource=function["FunctionArn"])["Tags"]
tags.should.equal({"key1": "val1", "key2": "val2"})
result = conn.get_function(FunctionName=function_name)
result.should.have.key("Tags").equals({"key1": "val1", "key2": "val2"})
@mock_lambda
def test_tags_not_found():
"""
Test list_tags and tag_resource when the lambda with the given arn does not exist
"""
conn = boto3.client("lambda", _lambda_region)
conn.list_tags.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID)
).should.throw(botocore.client.ClientError)
conn.tag_resource.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
Tags=dict(spam="eggs"),
).should.throw(botocore.client.ClientError)
conn.untag_resource.when.called_with(
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
TagKeys=["spam"],
).should.throw(botocore.client.ClientError)