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

View File

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

View File

@ -35,6 +35,27 @@ class PreconditionFailedException(JsonRESTError):
super().__init__("PreconditionFailedException", message) 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): class UnknownPolicyException(LambdaClientError):
code = 404 code = 404
@ -43,10 +64,3 @@ class UnknownPolicyException(LambdaClientError):
"ResourceNotFoundException", "ResourceNotFoundException",
"No policy is associated with the given resource.", "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 import requests.exceptions
from moto.awslambda.policy import Policy 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.core.exceptions import RESTError
from moto.iam.models import iam_backend from moto.iam.models import iam_backend
from moto.iam.exceptions import IAMNotFoundException from moto.iam.exceptions import IAMNotFoundException
@ -36,7 +36,9 @@ from .exceptions import (
CrossAccountNotAllowed, CrossAccountNotAllowed,
InvalidRoleFormat, InvalidRoleFormat,
InvalidParameterValueException, InvalidParameterValueException,
UnknownLayerException,
UnknownFunctionException, UnknownFunctionException,
UnknownAliasException,
) )
from .utils import ( from .utils import (
make_function_arn, make_function_arn,
@ -50,13 +52,11 @@ from moto.dynamodb import dynamodb_backends
from moto.dynamodbstreams import dynamodbstreams_backends from moto.dynamodbstreams import dynamodbstreams_backends
from moto.core import ACCOUNT_ID from moto.core import ACCOUNT_ID
from moto.utilities.docker_utilities import DockerModel, parse_image_ref from moto.utilities.docker_utilities import DockerModel, parse_image_ref
from tempfile import TemporaryDirectory
from uuid import uuid4
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
from tempfile import TemporaryDirectory
except ImportError:
from backports.tempfile import TemporaryDirectory
docker_3 = docker.__version__[0] >= "3" docker_3 = docker.__version__[0] >= "3"
@ -110,25 +110,26 @@ class _DockerDataVolumeContext:
def __enter__(self): def __enter__(self):
# See if volume is already known # See if volume is already known
with self.__class__._lock: 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 self._vol_ref.refcount += 1
if self._vol_ref.refcount > 1: if self._vol_ref.refcount > 1:
return self return self
# See if the volume already exists # See if the volume already exists
for vol in self._lambda_func.docker_client.volumes.list(): 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 self._vol_ref.volume = vol
return self return self
# It doesn't exist so we need to create it # It doesn't exist so we need to create it
self._vol_ref.volume = self._lambda_func.docker_client.volumes.create( 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: if docker_3:
volumes = {self.name: {"bind": "/tmp/data", "mode": "rw"}} volumes = {self.name: {"bind": "/tmp/data", "mode": "rw"}}
else: else:
volumes = {self.name: "/tmp/data"} volumes = {self.name: "/tmp/data"}
self._lambda_func.docker_client.images.pull( self._lambda_func.docker_client.images.pull(
":".join(parse_image_ref("alpine")) ":".join(parse_image_ref("alpine"))
) )
@ -164,7 +165,17 @@ def _zipfile_content(zipfile):
except Exception: except Exception:
to_unzip_code = base64.b64decode(zipfile) 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): def _validate_s3_bucket_and_key(data):
@ -228,15 +239,21 @@ class LayerVersion(CloudFormationModel):
self._layer = None self._layer = None
if "ZipFile" in self.content: 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: else:
key = _validate_s3_bucket_and_key(self.content) key = _validate_s3_bucket_and_key(self.content)
if key: if key:
self.code_bytes = key.value (
self.code_size = key.size self.code_bytes,
self.code_sha_256 = hashlib.sha256(key.value).hexdigest() self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
@property @property
def arn(self): def arn(self):
@ -251,7 +268,13 @@ class LayerVersion(CloudFormationModel):
def get_layer_version(self): def get_layer_version(self):
return { return {
"Content": {
"Location": "s3://",
"CodeSha256": self.code_sha_256,
"CodeSize": self.code_size,
},
"Version": self.version, "Version": self.version,
"LayerArn": self._layer.layer_arn,
"LayerVersionArn": self.arn, "LayerVersionArn": self.arn,
"CreatedDate": self.created_date, "CreatedDate": self.created_date,
"CompatibleRuntimes": self.compatible_runtimes, "CompatibleRuntimes": self.compatible_runtimes,
@ -288,6 +311,38 @@ class LayerVersion(CloudFormationModel):
return layer_version 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): class Layer(object):
def __init__(self, name, region): def __init__(self, name, region):
self.region = region self.region = region
@ -302,6 +357,9 @@ class Layer(object):
layer_version.attach(self, self._latest_version) layer_version.attach(self, self._latest_version)
self.layer_versions[str(self._latest_version)] = layer_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): def to_dict(self):
return { return {
"LayerName": self.name, "LayerName": self.name,
@ -338,6 +396,7 @@ class LambdaFunction(CloudFormationModel, DockerModel):
self.signing_profile_version_arn = spec.get("SigningProfileVersionArn") self.signing_profile_version_arn = spec.get("SigningProfileVersionArn")
self.signing_job_arn = spec.get("SigningJobArn") self.signing_job_arn = spec.get("SigningJobArn")
self.code_signing_config_arn = spec.get("CodeSigningConfigArn") 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) 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") self.last_modified = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
if "ZipFile" in self.code: 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 # TODO: we should be putting this in a lambda bucket
self.code["UUID"] = str(uuid.uuid4()) self.code["UUID"] = str(uuid.uuid4())
@ -361,9 +423,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
else: else:
key = _validate_s3_bucket_and_key(self.code) key = _validate_s3_bucket_and_key(self.code)
if key: if key:
self.code_bytes = key.value (
self.code_size = key.size self.code_bytes,
self.code_sha_256 = hashlib.sha256(key.value).hexdigest() self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
else: else:
self.code_bytes = "" self.code_bytes = ""
self.code_size = 0 self.code_size = 0
@ -378,6 +443,8 @@ class LambdaFunction(CloudFormationModel, DockerModel):
else: else:
self.tags = dict() self.tags = dict()
self._aliases = dict()
def set_version(self, version): def set_version(self, version):
self.function_arn = make_function_ver_arn( self.function_arn = make_function_ver_arn(
self.region, ACCOUNT_ID, self.function_name, version self.region, ACCOUNT_ID, self.function_name, version
@ -420,7 +487,7 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"FunctionName": self.function_name, "FunctionName": self.function_name,
} }
def get_configuration(self): def get_configuration(self, on_create=False):
config = { config = {
"CodeSha256": self.code_sha_256, "CodeSha256": self.code_sha_256,
"CodeSize": self.code_size, "CodeSize": self.code_size,
@ -440,7 +507,11 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"Layers": self.layers, "Layers": self.layers,
"SigningProfileVersionArn": self.signing_profile_version_arn, "SigningProfileVersionArn": self.signing_profile_version_arn,
"SigningJobArn": self.signing_job_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: if self.environment_vars:
config["Environment"] = {"Variables": self.environment_vars} config["Environment"] = {"Variables": self.environment_vars}
@ -456,6 +527,8 @@ class LambdaFunction(CloudFormationModel, DockerModel):
}, },
"Configuration": self.get_configuration(), "Configuration": self.get_configuration(),
} }
if self.tags:
code["Tags"] = self.tags
if self.reserved_concurrency: if self.reserved_concurrency:
code.update( code.update(
{ {
@ -496,19 +569,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
if "ZipFile" in updated_spec: if "ZipFile" in updated_spec:
self.code["ZipFile"] = updated_spec["ZipFile"] 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 self.code_bytes,
# in both places now self.code_size,
try: self.code_sha_256,
to_unzip_code = base64.b64decode( self.code_digest,
bytes(updated_spec["ZipFile"], "utf-8") ) = _zipfile_content(updated_spec["ZipFile"])
)
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()
# TODO: we should be putting this in a lambda bucket # TODO: we should be putting this in a lambda bucket
self.code["UUID"] = str(uuid.uuid4()) 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.", "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.",
) )
if key: if key:
self.code_bytes = key.value (
self.code_size = key.size self.code_bytes,
self.code_sha_256 = hashlib.sha256(key.value).hexdigest() self.code_size,
self.code_sha_256,
self.code_digest,
) = _s3_content(key)
self.code["S3Bucket"] = updated_spec["S3Bucket"] self.code["S3Bucket"] = updated_spec["S3Bucket"]
self.code["S3Key"] = updated_spec["S3Key"] self.code["S3Key"] = updated_spec["S3Key"]
@ -781,6 +850,32 @@ class LambdaFunction(CloudFormationModel, DockerModel):
def delete(self, region): def delete(self, region):
lambda_backends[region].delete_function(self.function_name) 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): class EventSourceMapping(CloudFormationModel):
def __init__(self, spec): def __init__(self, spec):
@ -934,8 +1029,9 @@ class LambdaVersion(CloudFormationModel):
class LambdaStorage(object): class LambdaStorage(object):
def __init__(self, region_name): def __init__(self, region_name):
# Format 'func_name' {'alias': {}, 'versions': []} # Format 'func_name' {'versions': []}
self._functions = {} self._functions = {}
self._aliases = dict()
self._arns = weakref.WeakValueDictionary() self._arns = weakref.WeakValueDictionary()
self.region_name = region_name self.region_name = region_name
@ -950,8 +1046,25 @@ class LambdaStorage(object):
except IndexError: except IndexError:
return None return None
def _get_alias(self, name, alias): def delete_alias(self, name, function_name):
return self._functions[name]["alias"].get(alias, None) 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): def get_function_by_name(self, name, qualifier=None):
if name not in self._functions: if name not in self._functions:
@ -974,6 +1087,11 @@ class LambdaStorage(object):
return [latest] + self._functions[name]["versions"] return [latest] + self._functions[name]["versions"]
def get_arn(self, arn): 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) return self._arns.get(arn, None)
def get_function_by_name_or_arn(self, name_or_arn, qualifier=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: if fn.function_name in self._functions:
self._functions[fn.function_name]["latest"] = fn self._functions[fn.function_name]["latest"] = fn
else: else:
self._functions[fn.function_name] = { self._functions[fn.function_name] = {"latest": fn, "versions": []}
"latest": fn,
"versions": [],
"alias": weakref.WeakValueDictionary(),
}
# instantiate a new policy for this version of the lambda # instantiate a new policy for this version of the lambda
fn.policy = Policy(fn) fn.policy = Policy(fn)
self._arns[fn.function_arn] = fn self._arns[fn.function_arn] = fn
@ -1122,6 +1236,17 @@ class LayerStorage(object):
def list_layers(self): def list_layers(self):
return [layer.to_dict() for layer in self._layers.values()] 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): def get_layer_versions(self, layer_name):
if layer_name in self._layers: if layer_name in self._layers:
return list(iter(self._layers[layer_name].layer_versions.values())) return list(iter(self._layers[layer_name].layer_versions.values()))
@ -1188,7 +1313,7 @@ class LambdaBackend(BaseBackend):
""" """
def __init__(self, region_name): def __init__(self, region_name):
self._lambdas = LambdaStorage(region_name) self._lambdas = LambdaStorage(region_name=region_name)
self._event_source_mappings = {} self._event_source_mappings = {}
self._layers = LayerStorage() self._layers = LayerStorage()
self.region_name = region_name self.region_name = region_name
@ -1205,6 +1330,29 @@ class LambdaBackend(BaseBackend):
service_region, zones, "lambda" 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): def create_function(self, spec):
function_name = spec.get("FunctionName", None) function_name = spec.get("FunctionName", None)
if function_name is None: if function_name is None:
@ -1274,9 +1422,7 @@ class LambdaBackend(BaseBackend):
required = ["LayerName", "Content"] required = ["LayerName", "Content"]
for param in required: for param in required:
if not spec.get(param): if not spec.get(param):
raise RESTError( raise InvalidParameterValueException("Missing {}".format(param))
"InvalidParameterValueException", "Missing {}".format(param)
)
layer_version = LayerVersion(spec, self.region_name) layer_version = LayerVersion(spec, self.region_name)
self._layers.put_layer_version(layer_version) self._layers.put_layer_version(layer_version)
return layer_version return layer_version
@ -1284,6 +1430,12 @@ class LambdaBackend(BaseBackend):
def list_layers(self): def list_layers(self):
return self._layers.list_layers() 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): def get_layer_versions(self, layer_name):
return self._layers.get_layer_versions(layer_name) return self._layers.get_layer_versions(layer_name)
@ -1485,10 +1637,8 @@ class LambdaBackend(BaseBackend):
def get_policy(self, function_name): def get_policy(self, function_name):
fn = self.get_function(function_name) fn = self.get_function(function_name)
return fn.policy.get_policy() if not fn:
raise UnknownFunctionException(function_name)
def get_policy_wire_format(self, function_name):
fn = self.get_function(function_name)
return fn.policy.wire_format() return fn.policy.wire_format()
def update_function_code(self, function_name, qualifier, body): def update_function_code(self, function_name, qualifier, body):

View File

@ -47,6 +47,20 @@ class LambdaResponse(BaseResponse):
else: else:
raise ValueError("Cannot handle request") 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): def event_source_mapping(self, request, full_url, headers):
self.setup_class(request, full_url, headers) self.setup_class(request, full_url, headers)
path = request.path if hasattr(request, "path") else path_url(request.url) path = request.path if hasattr(request, "path") else path_url(request.url)
@ -65,6 +79,13 @@ class LambdaResponse(BaseResponse):
if request.method == "GET": if request.method == "GET":
return self._list_layers() 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): def layers_versions(self, request, full_url, headers):
self.setup_class(request, full_url, headers) self.setup_class(request, full_url, headers)
if request.method == "GET": if request.method == "GET":
@ -182,11 +203,8 @@ class LambdaResponse(BaseResponse):
def _get_policy(self, request): def _get_policy(self, request):
path = request.path if hasattr(request, "path") else path_url(request.url) path = request.path if hasattr(request, "path") else path_url(request.url)
function_name = unquote(path.split("/")[-2]) function_name = unquote(path.split("/")[-2])
if self.lambda_backend.get_function(function_name): out = self.lambda_backend.get_policy(function_name)
out = self.lambda_backend.get_policy_wire_format(function_name)
return 200, {}, out return 200, {}, out
else:
return 404, {}, "{}"
def _del_policy(self, request, querystring): def _del_policy(self, request, querystring):
path = request.path if hasattr(request, "path") else path_url(request.url) path = request.path if hasattr(request, "path") else path_url(request.url)
@ -270,7 +288,7 @@ class LambdaResponse(BaseResponse):
def _create_function(self): def _create_function(self):
fn = self.lambda_backend.create_function(self.json_body) 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) return 201, {}, json.dumps(config)
def _create_event_source_mapping(self): def _create_event_source_mapping(self):
@ -466,6 +484,20 @@ class LambdaResponse(BaseResponse):
layers = self.lambda_backend.list_layers() layers = self.lambda_backend.list_layers()
return 200, {}, json.dumps({"Layers": 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): def _get_layer_versions(self):
layer_name = self.path.rsplit("/", 2)[-2] layer_name = self.path.rsplit("/", 2)[-2]
layer_versions = self.lambda_backend.get_layer_versions(layer_name) 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) layer_version = self.lambda_backend.publish_layer_version(spec)
config = layer_version.get_layer_version() config = layer_version.get_layer_version()
return 201, {}, json.dumps(config) 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 = { url_paths = {
r"{0}/(?P<api_version>[^/]+)/functions/?$": response.root, 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_:%-]+)/?$": 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>[^/]+)/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/?$": response.event_source_mappings,
r"{0}/(?P<api_version>[^/]+)/event-source-mappings/(?P<UUID>[\w_-]+)/?$": response.event_source_mapping, 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>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/concurrency/?$": response.function_concurrency,
r"{0}/(?P<api_version>[^/]+)/layers/?$": response.list_layers, 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/?$": 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 TestAccAWSKinesisStream
TestAccAWSKmsAlias TestAccAWSKmsAlias
TestAccAWSKmsSecretDataSource TestAccAWSKmsSecretDataSource
TestAccAWSLambdaAlias
TestAccAWSLambdaLayerVersion
TestAccAWSMq TestAccAWSMq
TestAccAWSNatGateway TestAccAWSNatGateway
TestAccAWSPartition TestAccAWSPartition
@ -133,5 +135,7 @@ TestAccDataSourceAWSEFSAccessPoint
TestAccDataSourceAWSEFSAccessPoints TestAccDataSourceAWSEFSAccessPoints
TestAccDataSourceAwsEfsFileSystem TestAccDataSourceAwsEfsFileSystem
TestAccDataSourceAwsEfsMountTarget TestAccDataSourceAwsEfsMountTarget
TestAccDataSourceAWSLambdaLayerVersion
TestAccDataSourceAwsLambdaInvocation
TestAccDataSourceAwsNetworkInterface_ TestAccDataSourceAwsNetworkInterface_
TestValidateSSMDocumentPermissions TestValidateSSMDocumentPermissions

View File

@ -1,7 +1,7 @@
import base64
import botocore.client import botocore.client
import boto3 import boto3
import hashlib import hashlib
import json
import sure # noqa # pylint: disable=unused-import import sure # noqa # pylint: disable=unused-import
import pytest import pytest
@ -122,37 +122,19 @@ def test_create_function_from_aws_bucket():
Publish=True, Publish=True,
VpcConfig={"SecurityGroupIds": ["sg-123abc"], "SubnetIds": ["subnet-123abc"]}, VpcConfig={"SecurityGroupIds": ["sg-123abc"], "SubnetIds": ["subnet-123abc"]},
) )
# this is hard to match against, so remove it
result["ResponseMetadata"].pop("HTTPHeaders", None) result.should.have.key("FunctionName").equals(function_name)
# Botocore inserts retry attempts not seen in Python27 result.should.have.key("FunctionArn").equals(
result["ResponseMetadata"].pop("RetryAttempts", None) "arn:aws:lambda:{}:{}:function:{}".format(
result.pop("LastModified")
result.should.equal(
{
"FunctionName": function_name,
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}".format(
_lambda_region, ACCOUNT_ID, function_name _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("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 @mock_lambda
@ -191,16 +173,46 @@ def test_create_function_from_zipfile():
"Description": "test lambda function", "Description": "test lambda function",
"Timeout": 3, "Timeout": 3,
"MemorySize": 128, "MemorySize": 128,
"CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSha256": base64.b64encode(hashlib.sha256(zip_content).digest()).decode(
"utf-8"
),
"Version": "1", "Version": "1",
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []}, "VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"ResponseMetadata": {"HTTPStatusCode": 201}, "ResponseMetadata": {"HTTPStatusCode": 201},
"State": "Active", "State": "Active",
"Layers": [], "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_lambda
@mock_s3 @mock_s3
@freeze_time("2015-01-01 00:00:00") @freeze_time("2015-01-01 00:00:00")
@ -243,7 +255,7 @@ def test_get_function():
result["Code"]["RepositoryType"].should.equal("S3") result["Code"]["RepositoryType"].should.equal("S3")
result["Configuration"]["CodeSha256"].should.equal( 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"]["CodeSize"].should.equal(len(zip_content))
result["Configuration"]["Description"].should.equal("test lambda function") 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 = 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["CodeSize"].should.equal(len(zip_content))
result["Description"].should.equal("test lambda function") result["Description"].should.equal("test lambda function")
result.should.contain("FunctionArn") result.should.contain("FunctionArn")
@ -600,7 +614,9 @@ def test_list_create_list_get_delete_list():
"RepositoryType": "S3", "RepositoryType": "S3",
}, },
"Configuration": { "Configuration": {
"CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSha256": base64.b64encode(hashlib.sha256(zip_content).digest()).decode(
"utf-8"
),
"CodeSize": len(zip_content), "CodeSize": len(zip_content),
"Description": "test lambda function", "Description": "test lambda function",
"FunctionName": function_name, "FunctionName": function_name,
@ -613,6 +629,8 @@ def test_list_create_list_get_delete_list():
"VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []}, "VpcConfig": {"SecurityGroupIds": [], "SubnetIds": []},
"State": "Active", "State": "Active",
"Layers": [], "Layers": [],
"LastUpdateStatus": "Successful",
"TracingConfig": {"Mode": "PassThrough"},
}, },
"ResponseMetadata": {"HTTPStatusCode": 200}, "ResponseMetadata": {"HTTPStatusCode": 200},
} }
@ -673,90 +691,6 @@ def test_list_create_list_get_delete_list():
func_names.shouldnt.contain(function_name) 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 @mock_lambda
@freeze_time("2015-01-01 00:00:00") @freeze_time("2015-01-01 00:00:00")
def test_get_function_created_with_zipfile(): 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 = conn.get_function(FunctionName=function_name)
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2 assert len(response["Code"]) == 2
@ -784,100 +717,26 @@ def test_get_function_created_with_zipfile():
assert response["Code"]["Location"].startswith( assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region) "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
) )
response["Configuration"].should.equal( response.should.have.key("Configuration")
{ config = response["Configuration"]
"CodeSha256": hashlib.sha256(zip_content).hexdigest(), config.should.have.key("CodeSha256").equals(
"CodeSize": len(zip_content), base64.b64encode(hashlib.sha256(zip_content).digest()).decode("utf-8")
"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": [],
}
) )
config.should.have.key("CodeSize").equals(len(zip_content))
config.should.have.key("Description").equals("test lambda function")
@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"]) config.should.have.key("FunctionArn").equals(
@mock_lambda f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}"
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] config.should.have.key("FunctionName").equals(function_name)
config.should.have.key("Handler").equals("lambda_function.handler")
response = conn.add_permission( config.should.have.key("MemorySize").equals(128)
FunctionName=name_or_arn, config.should.have.key("Role").equals(get_role_name())
StatementId="1", config.should.have.key("Runtime").equals("python2.7")
Action="lambda:InvokeFunction", config.should.have.key("Timeout").equals(3)
Principal="432143214321", config.should.have.key("Version").equals("$LATEST")
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld", config.should.have.key("State").equals("Active")
SourceAccount="123412341234", config.should.have.key("Layers").equals([])
EventSourceToken="blah", config.should.have.key("LastUpdateStatus").equals("Successful")
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"
@mock_lambda @mock_lambda
@ -1085,7 +944,6 @@ def test_update_function_zip(key):
) )
response = conn.get_function(FunctionName=function_name, Qualifier="2") response = conn.get_function(FunctionName=function_name, Qualifier="2")
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2 assert len(response["Code"]) == 2
@ -1093,26 +951,16 @@ def test_update_function_zip(key):
assert response["Code"]["Location"].startswith( assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region) "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
) )
response["Configuration"].should.equal(
{ config = response["Configuration"]
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(), config.should.have.key("CodeSize").equals(len(zip_content_two))
"CodeSize": len(zip_content_two), config.should.have.key("Description").equals("test lambda function")
"Description": "test lambda function", config.should.have.key("FunctionArn").equals(
"FunctionArn": "arn:aws:lambda:{}:{}:function:{}:2".format( f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}:2"
_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.should.have.key("FunctionName").equals(function_name)
config.should.have.key("Version").equals("2")
config.should.have.key("LastUpdateStatus").equals("Successful")
@mock_lambda @mock_lambda
@ -1131,7 +979,7 @@ def test_update_function_s3():
conn = boto3.client("lambda", _lambda_region) conn = boto3.client("lambda", _lambda_region)
function_name = str(uuid4())[0:6] function_name = str(uuid4())[0:6]
fxn = conn.create_function( conn.create_function(
FunctionName=function_name, FunctionName=function_name,
Runtime="python2.7", Runtime="python2.7",
Role=get_role_name(), Role=get_role_name(),
@ -1154,7 +1002,6 @@ def test_update_function_s3():
) )
response = conn.get_function(FunctionName=function_name, Qualifier="2") response = conn.get_function(FunctionName=function_name, Qualifier="2")
response["Configuration"].pop("LastModified")
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
assert len(response["Code"]) == 2 assert len(response["Code"]) == 2
@ -1162,26 +1009,19 @@ def test_update_function_s3():
assert response["Code"]["Location"].startswith( assert response["Code"]["Location"].startswith(
"s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region) "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com".format(_lambda_region)
) )
response["Configuration"].should.equal(
{ config = response["Configuration"]
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(), config.should.have.key("CodeSha256").equals(
"CodeSize": len(zip_content_two), base64.b64encode(hashlib.sha256(zip_content_two).digest()).decode("utf-8")
"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.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 @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 @mock_lambda
def test_remove_unknown_permission_throws_error(): def test_remove_unknown_permission_throws_error():
conn = boto3.client("lambda", _lambda_region) 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 botocore.exceptions import ClientError
from freezegun import freeze_time from freezegun import freeze_time
from moto import mock_lambda, mock_s3 from moto import mock_lambda, mock_s3
from moto.core.exceptions import RESTError
from moto.sts.models import ACCOUNT_ID from moto.sts.models import ACCOUNT_ID
from uuid import uuid4 from uuid import uuid4
@ -15,6 +14,23 @@ _lambda_region = "us-west-2"
boto3.setup_default_session(region_name=_lambda_region) 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_lambda
@mock_s3 @mock_s3
@freeze_time("2015-01-01 00:00:00") @freeze_time("2015-01-01 00:00:00")
@ -31,13 +47,6 @@ def test_get_lambda_layers():
conn = boto3.client("lambda", _lambda_region) conn = boto3.client("lambda", _lambda_region)
layer_name = str(uuid4())[0:6] 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( conn.publish_layer_version(
LayerName=layer_name, LayerName=layer_name,
Content={"ZipFile": get_test_zip_file1()}, Content={"ZipFile": get_test_zip_file1()},
@ -123,3 +132,93 @@ def test_get_lambda_layers():
Environment={"Variables": {"test_variable": "test_value"}}, Environment={"Variables": {"test_variable": "test_value"}},
Layers=[(expected_arn + "3")], 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)