2016-02-16 20:15:34 +00:00
import base64
2023-11-30 15:55:51 +00:00
import calendar
2017-11-26 21:28:28 +00:00
import copy
2016-02-16 20:15:34 +00:00
import hashlib
2016-10-06 09:52:23 +00:00
import io
2023-11-30 15:55:51 +00:00
import json
2017-09-27 23:04:58 +00:00
import logging
2017-05-24 12:54:00 +00:00
import os
2017-09-27 23:04:58 +00:00
import re
import tarfile
import threading
2023-11-30 15:55:51 +00:00
import time
2023-09-05 17:31:13 +00:00
import warnings
2023-11-30 15:55:51 +00:00
import weakref
import zipfile
from collections import defaultdict
from datetime import datetime
from gzip import GzipFile
from sys import platform
2024-01-06 19:20:57 +00:00
from typing import Any , Dict , Iterable , List , Optional , Tuple , TypedDict , Union
2023-11-30 15:55:51 +00:00
2020-11-08 15:16:53 +00:00
import requests . exceptions
2016-10-06 14:14:47 +00:00
2023-11-30 15:55:51 +00:00
from moto import settings
2020-01-23 18:46:24 +00:00
from moto . awslambda . policy import Policy
2024-01-17 10:05:37 +00:00
from moto . core . base_backend import BackendDict , BaseBackend
from moto . core . common_models import BaseModel , CloudFormationModel
2017-11-26 21:28:28 +00:00
from moto . core . exceptions import RESTError
2023-11-30 15:55:51 +00:00
from moto . core . utils import iso_8601_datetime_with_nanoseconds , unix_time_millis , utcnow
from moto . dynamodb import dynamodb_backends
from moto . dynamodbstreams import dynamodbstreams_backends
2022-11-23 13:16:33 +00:00
from moto . ecr . exceptions import ImageNotFoundException
2023-11-30 15:55:51 +00:00
from moto . ecr . models import ecr_backends
from moto . iam . exceptions import IAMNotFoundException
from moto . iam . models import iam_backends
2024-01-30 20:51:15 +00:00
from moto . kinesis . models import KinesisBackend , kinesis_backends
2017-09-27 23:04:58 +00:00
from moto . logs . models import logs_backends
2022-09-28 09:35:12 +00:00
from moto . moto_api . _internal import mock_random as random
2017-05-24 12:54:00 +00:00
from moto . s3 . exceptions import MissingBucket , MissingKey
2023-11-30 15:55:51 +00:00
from moto . s3 . models import FakeKey , s3_backends
2024-01-30 20:51:15 +00:00
from moto . sqs . models import sqs_backends
2023-11-30 15:55:51 +00:00
from moto . utilities . docker_utilities import DockerModel
from moto . utilities . utils import load_resource_as_bytes
2019-11-17 10:59:20 +00:00
from . exceptions import (
2023-07-01 10:32:33 +00:00
ConflictException ,
2019-11-17 10:59:20 +00:00
CrossAccountNotAllowed ,
2024-03-19 21:49:46 +00:00
GenericResourcNotFound ,
2019-11-17 10:59:20 +00:00
InvalidParameterValueException ,
2023-11-30 15:55:51 +00:00
InvalidRoleFormat ,
UnknownAliasException ,
2023-12-01 23:07:52 +00:00
UnknownEventConfig ,
2023-11-30 15:55:51 +00:00
UnknownFunctionException ,
2022-03-17 12:32:31 +00:00
UnknownLayerException ,
2023-06-15 11:05:06 +00:00
UnknownLayerVersionException ,
2023-07-06 16:45:29 +00:00
ValidationException ,
2019-11-17 10:59:20 +00:00
)
2021-01-17 15:28:49 +00:00
from . utils import (
make_function_arn ,
make_function_ver_arn ,
make_layer_arn ,
make_layer_ver_arn ,
split_layer_arn ,
)
2016-02-12 19:39:20 +00:00
2017-09-27 23:04:58 +00:00
logger = logging . getLogger ( __name__ )
2018-06-01 03:05:50 +00:00
2024-01-06 19:20:57 +00:00
class LayerDataType ( TypedDict ) :
Arn : str
CodeSize : int
2023-09-04 21:24:39 +00:00
def zip2tar ( zip_bytes : bytes ) - > io . BytesIO :
tarstream = io . BytesIO ( )
2023-09-11 22:23:44 +00:00
timeshift = int ( ( datetime . now ( ) - utcnow ( ) ) . total_seconds ( ) )
2023-09-04 21:24:39 +00:00
tarf = tarfile . TarFile ( fileobj = tarstream , mode = " w " )
with zipfile . ZipFile ( io . BytesIO ( zip_bytes ) , " r " ) as zipf :
for zipinfo in zipf . infolist ( ) :
if zipinfo . is_dir ( ) :
continue
2017-09-27 23:04:58 +00:00
2023-09-04 21:24:39 +00:00
tarinfo = tarfile . TarInfo ( name = zipinfo . filename )
tarinfo . size = zipinfo . file_size
tarinfo . mtime = calendar . timegm ( zipinfo . date_time ) - timeshift
infile = zipf . open ( zipinfo . filename )
tarf . addfile ( tarinfo , infile )
2017-09-27 23:04:58 +00:00
2023-09-04 21:24:39 +00:00
tarstream . seek ( 0 )
return tarstream
2017-09-27 23:04:58 +00:00
2023-09-27 18:34:30 +00:00
def file2tar ( file_content : bytes , file_name : str ) - > io . BytesIO :
tarstream = io . BytesIO ( )
tarf = tarfile . TarFile ( fileobj = tarstream , mode = " w " )
tarinfo = tarfile . TarInfo ( name = file_name )
tarinfo . size = len ( file_content )
tarf . addfile ( tarinfo , io . BytesIO ( file_content ) )
tarstream . seek ( 0 )
return tarstream
2017-09-27 23:04:58 +00:00
class _VolumeRefCount :
__slots__ = " refcount " , " volume "
2022-10-22 11:40:20 +00:00
def __init__ ( self , refcount : int , volume : Any ) :
2017-09-27 23:04:58 +00:00
self . refcount = refcount
self . volume = volume
2016-02-12 19:39:20 +00:00
2017-09-27 23:04:58 +00:00
class _DockerDataVolumeContext :
2022-10-22 11:40:20 +00:00
# {sha256: _VolumeRefCount}
_data_vol_map : Dict [ str , _VolumeRefCount ] = defaultdict (
2017-09-27 23:04:58 +00:00
lambda : _VolumeRefCount ( 0 , None )
2022-10-22 11:40:20 +00:00
)
2017-09-27 23:04:58 +00:00
_lock = threading . Lock ( )
2022-10-22 11:40:20 +00:00
def __init__ ( self , lambda_func : " LambdaFunction " ) :
2017-09-27 23:04:58 +00:00
self . _lambda_func = lambda_func
2022-10-22 11:40:20 +00:00
self . _vol_ref : Optional [ _VolumeRefCount ] = None
2017-09-27 23:04:58 +00:00
@property
2022-10-22 11:40:20 +00:00
def name ( self ) - > str :
return self . _vol_ref . volume . name # type: ignore[union-attr]
2017-09-27 23:04:58 +00:00
2022-10-22 11:40:20 +00:00
def __enter__ ( self ) - > " _DockerDataVolumeContext " :
2017-09-27 23:04:58 +00:00
# See if volume is already known
with self . __class__ . _lock :
2022-03-17 12:32:31 +00:00
self . _vol_ref = self . __class__ . _data_vol_map [ self . _lambda_func . code_digest ]
2017-09-27 23:04:58 +00:00
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 ( ) :
2022-03-17 12:32:31 +00:00
if vol . name == self . _lambda_func . code_digest :
2017-09-27 23:04:58 +00:00
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 (
2022-03-17 12:32:31 +00:00
self . _lambda_func . code_digest
2017-09-27 23:04:58 +00:00
)
2023-03-18 10:35:48 +00:00
volumes = { self . name : { " bind " : settings . LAMBDA_DATA_DIR , " mode " : " rw " } }
2022-03-17 12:32:31 +00:00
2023-04-13 09:25:47 +00:00
self . _lambda_func . ensure_image_exists ( " busybox " )
2018-06-01 02:39:22 +00:00
container = self . _lambda_func . docker_client . containers . run (
2023-04-13 09:25:47 +00:00
" busybox " , " sleep 100 " , volumes = volumes , detach = True
2018-06-01 02:39:22 +00:00
)
2017-09-27 23:04:58 +00:00
try :
2023-09-04 21:24:39 +00:00
with zip2tar ( self . _lambda_func . code_bytes ) as stream :
container . put_archive ( settings . LAMBDA_DATA_DIR , stream )
2024-01-17 10:05:37 +00:00
if settings . is_test_proxy_mode ( ) :
2023-09-27 18:34:30 +00:00
ca_cert = load_resource_as_bytes ( __name__ , " ../moto_proxy/ca.crt " )
with file2tar ( ca_cert , " ca.crt " ) as cert_stream :
container . put_archive ( settings . LAMBDA_DATA_DIR , cert_stream )
2017-09-27 23:04:58 +00:00
finally :
container . remove ( force = True )
return self
2022-10-22 11:40:20 +00:00
def __exit__ ( self , exc_type : Any , exc_val : Any , exc_tb : Any ) - > None :
2017-09-27 23:04:58 +00:00
with self . __class__ . _lock :
2022-10-22 11:40:20 +00:00
self . _vol_ref . refcount - = 1 # type: ignore[union-attr]
if self . _vol_ref . refcount == 0 : # type: ignore[union-attr]
2023-11-07 22:48:26 +00:00
import docker . errors
2017-09-27 23:04:58 +00:00
try :
2022-10-22 11:40:20 +00:00
self . _vol_ref . volume . remove ( ) # type: ignore[union-attr]
2017-09-27 23:04:58 +00:00
except docker . errors . APIError as e :
if e . status_code != 409 :
raise
raise # multiple processes trying to use same volume?
2023-09-05 17:31:13 +00:00
class _DockerDataVolumeLayerContext :
_data_vol_map : Dict [ str , _VolumeRefCount ] = defaultdict (
lambda : _VolumeRefCount ( 0 , None )
)
_lock = threading . Lock ( )
def __init__ ( self , lambda_func : " LambdaFunction " ) :
self . _lambda_func = lambda_func
2024-01-06 19:20:57 +00:00
self . _layers : List [ LayerDataType ] = self . _lambda_func . layers
2023-09-05 17:31:13 +00:00
self . _vol_ref : Optional [ _VolumeRefCount ] = None
@property
def name ( self ) - > str :
return self . _vol_ref . volume . name # type: ignore[union-attr]
@property
def hash ( self ) - > str :
return " - " . join (
[
layer [ " Arn " ] . split ( " layer: " ) [ - 1 ] . replace ( " : " , " _ " )
for layer in self . _layers
]
)
def __enter__ ( self ) - > " _DockerDataVolumeLayerContext " :
# See if volume is already known
with self . __class__ . _lock :
self . _vol_ref = self . __class__ . _data_vol_map [ self . hash ]
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 . hash :
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 . hash
)
# If we don't have any layers to apply, just return at this point
# When invoking the function, we will bind this empty volume
if len ( self . _layers ) == 0 :
return self
volumes = { self . name : { " bind " : " /opt " , " mode " : " rw " } }
self . _lambda_func . ensure_image_exists ( " busybox " )
container = self . _lambda_func . docker_client . containers . run (
" busybox " , " sleep 100 " , volumes = volumes , detach = True
)
backend : " LambdaBackend " = lambda_backends [ self . _lambda_func . account_id ] [
self . _lambda_func . region
]
try :
for layer in self . _layers :
try :
layer_zip = backend . layers_versions_by_arn ( # type: ignore[union-attr]
layer [ " Arn " ]
) . code_bytes
layer_tar = zip2tar ( layer_zip )
container . put_archive ( " /opt " , layer_tar )
except zipfile . BadZipfile as e :
warnings . warn ( f " Error extracting layer to Lambda: { e } " )
finally :
container . remove ( force = True )
return self
def __exit__ ( self , exc_type : Any , exc_val : Any , exc_tb : Any ) - > None :
with self . __class__ . _lock :
self . _vol_ref . refcount - = 1 # type: ignore[union-attr]
if self . _vol_ref . refcount == 0 : # type: ignore[union-attr]
2023-11-07 22:48:26 +00:00
import docker . errors
2023-09-05 17:31:13 +00:00
try :
self . _vol_ref . volume . remove ( ) # type: ignore[union-attr]
except docker . errors . APIError as e :
if e . status_code != 409 :
raise
raise # multiple processes trying to use same volume?
2022-10-22 11:40:20 +00:00
def _zipfile_content ( zipfile_content : Union [ str , bytes ] ) - > Tuple [ bytes , int , str , str ] :
2021-01-17 15:28:49 +00:00
try :
2022-10-22 11:40:20 +00:00
to_unzip_code = base64 . b64decode ( bytes ( zipfile_content , " utf-8 " ) ) # type: ignore[arg-type]
2021-01-17 15:28:49 +00:00
except Exception :
2022-10-04 16:28:30 +00:00
to_unzip_code = base64 . b64decode ( zipfile_content )
2021-01-17 15:28:49 +00:00
2022-03-17 12:32:31 +00:00
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
2022-10-22 11:40:20 +00:00
def _s3_content ( key : Any ) - > Tuple [ bytes , int , str , str ] :
2022-03-17 12:32:31 +00:00
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
2021-01-17 15:28:49 +00:00
2022-10-22 11:40:20 +00:00
def _validate_s3_bucket_and_key (
account_id : str , data : Dict [ str , Any ]
) - > Optional [ FakeKey ] :
2021-01-17 15:28:49 +00:00
key = None
try :
# FIXME: does not validate bucket region
2022-08-13 09:49:43 +00:00
key = s3_backends [ account_id ] [ " global " ] . get_object (
data [ " S3Bucket " ] , data [ " S3Key " ]
)
2021-01-17 15:28:49 +00:00
except MissingBucket :
if do_validate_s3 ( ) :
raise InvalidParameterValueException (
" Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist "
)
except MissingKey :
if do_validate_s3 ( ) :
raise ValueError (
" InvalidParameterValueException " ,
" Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist. " ,
)
return key
2023-12-01 23:07:52 +00:00
class EventInvokeConfig :
def __init__ ( self , arn : str , last_modified : str , config : Dict [ str , Any ] ) - > None :
self . config = config
self . validate_max ( )
self . validate ( )
self . arn = arn
self . last_modified = last_modified
def validate_max ( self ) - > None :
if " MaximumRetryAttempts " in self . config :
mra = self . config [ " MaximumRetryAttempts " ]
if mra > 2 :
raise ValidationException (
mra ,
" maximumRetryAttempts " ,
" Member must have value less than or equal to 2 " ,
)
# < 0 validation done by botocore
if " MaximumEventAgeInSeconds " in self . config :
mra = self . config [ " MaximumEventAgeInSeconds " ]
if mra > 21600 :
raise ValidationException (
mra ,
" maximumEventAgeInSeconds " ,
" Member must have value less than or equal to 21600 " ,
)
# < 60 validation done by botocore
def validate ( self ) - > None :
# https://docs.aws.amazon.com/lambda/latest/dg/API_OnSuccess.html
regex = r " ^$|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9 \ -])+:([a-z] {2} (-gov)?-[a-z]+- \ d {1} )?:( \ d {12} )?:(.*) "
pattern = re . compile ( regex )
if self . config [ " DestinationConfig " ] :
destination_config = self . config [ " DestinationConfig " ]
if (
" OnSuccess " in destination_config
and " Destination " in destination_config [ " OnSuccess " ]
) :
contents = destination_config [ " OnSuccess " ] [ " Destination " ]
if not pattern . match ( contents ) :
raise ValidationException (
contents ,
" destinationConfig.onSuccess.destination " ,
f " Member must satisfy regular expression pattern: { regex } " ,
)
if (
" OnFailure " in destination_config
and " Destination " in destination_config [ " OnFailure " ]
) :
contents = destination_config [ " OnFailure " ] [ " Destination " ]
if not pattern . match ( contents ) :
raise ValidationException (
contents ,
" destinationConfig.onFailure.destination " ,
f " Member must satisfy regular expression pattern: { regex } " ,
)
def response ( self ) - > Dict [ str , Any ] :
response = { " FunctionArn " : self . arn , " LastModified " : self . last_modified }
response . update ( self . config )
return response
2023-07-06 16:45:29 +00:00
class ImageConfig :
def __init__ ( self , config : Dict [ str , Any ] ) - > None :
self . cmd = config . get ( " Command " , [ ] )
self . entry_point = config . get ( " EntryPoint " , [ ] )
2023-08-01 21:42:12 +00:00
self . working_directory = config . get ( " WorkingDirectory " , None )
2023-07-06 16:45:29 +00:00
def response ( self ) - > Dict [ str , Any ] :
2023-08-01 21:42:12 +00:00
content = {
" Command " : self . cmd ,
" EntryPoint " : self . entry_point ,
}
if self . working_directory is not None :
content [ " WorkingDirectory " ] = self . working_directory
return dict ( content )
2023-07-06 16:45:29 +00:00
2021-02-15 10:31:33 +00:00
class Permission ( CloudFormationModel ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self , region : str ) :
2021-02-15 10:31:33 +00:00
self . region = region
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_name_type ( ) - > str :
2021-02-15 10:31:33 +00:00
return " Permission "
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_type ( ) - > str :
2021-02-15 10:31:33 +00:00
return " AWS::Lambda::Permission "
@classmethod
2022-10-22 11:40:20 +00:00
def create_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
* * kwargs : Any ,
) - > " Permission " :
2021-02-15 10:31:33 +00:00
properties = cloudformation_json [ " Properties " ]
2022-08-13 09:49:43 +00:00
backend = lambda_backends [ account_id ] [ region_name ]
2021-02-15 10:31:33 +00:00
fn = backend . get_function ( properties [ " FunctionName " ] )
fn . policy . add_statement ( raw = json . dumps ( properties ) )
return Permission ( region = region_name )
2021-01-17 15:28:49 +00:00
class LayerVersion ( CloudFormationModel ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self , spec : Dict [ str , Any ] , account_id : str , region : str ) :
2021-01-17 15:28:49 +00:00
# required
2022-08-13 09:49:43 +00:00
self . account_id = account_id
2021-01-17 15:28:49 +00:00
self . region = region
self . name = spec [ " LayerName " ]
self . content = spec [ " Content " ]
# optional
self . description = spec . get ( " Description " , " " )
2022-05-19 11:08:02 +00:00
self . compatible_architectures = spec . get ( " CompatibleArchitectures " , [ ] )
2021-01-17 15:28:49 +00:00
self . compatible_runtimes = spec . get ( " CompatibleRuntimes " , [ ] )
self . license_info = spec . get ( " LicenseInfo " , " " )
# auto-generated
2023-09-11 22:23:44 +00:00
self . created_date = utcnow ( ) . strftime ( " % Y- % m- %d % H: % M: % S " )
2022-10-22 11:40:20 +00:00
self . version : Optional [ int ] = None
2021-01-17 15:28:49 +00:00
self . _attached = False
2022-10-22 11:40:20 +00:00
self . _layer : Optional [ " Layer " ] = None
2021-01-17 15:28:49 +00:00
if " ZipFile " in self . content :
2022-03-17 12:32:31 +00:00
(
self . code_bytes ,
self . code_size ,
self . code_sha_256 ,
self . code_digest ,
) = _zipfile_content ( self . content [ " ZipFile " ] )
2021-01-17 15:28:49 +00:00
else :
2022-08-13 09:49:43 +00:00
key = _validate_s3_bucket_and_key ( account_id , data = self . content )
2021-01-17 15:28:49 +00:00
if key :
2022-03-17 12:32:31 +00:00
(
self . code_bytes ,
self . code_size ,
self . code_sha_256 ,
self . code_digest ,
2023-07-20 15:46:54 +00:00
) = _s3_content ( key )
2023-10-16 08:46:13 +00:00
else :
self . code_bytes = b " "
self . code_size = 0
self . code_sha_256 = " "
self . code_digest = " "
2021-01-17 15:28:49 +00:00
@property
2022-10-22 11:40:20 +00:00
def arn ( self ) - > str :
2021-01-17 15:28:49 +00:00
if self . version :
2022-05-08 22:25:40 +00:00
return make_layer_ver_arn (
2022-08-13 09:49:43 +00:00
self . region , self . account_id , self . name , self . version
2022-05-08 22:25:40 +00:00
)
2021-01-17 15:28:49 +00:00
raise ValueError ( " Layer version is not set " )
2022-10-22 11:40:20 +00:00
def attach ( self , layer : " Layer " , version : int ) - > None :
2021-01-17 15:28:49 +00:00
self . _attached = True
self . _layer = layer
self . version = version
2022-10-22 11:40:20 +00:00
def get_layer_version ( self ) - > Dict [ str , Any ] :
2021-01-17 15:28:49 +00:00
return {
2022-03-17 12:32:31 +00:00
" Content " : {
" Location " : " s3:// " ,
" CodeSha256 " : self . code_sha_256 ,
" CodeSize " : self . code_size ,
} ,
2021-01-17 15:28:49 +00:00
" Version " : self . version ,
2022-10-22 11:40:20 +00:00
" LayerArn " : self . _layer . layer_arn , # type: ignore[union-attr]
2021-01-17 15:28:49 +00:00
" LayerVersionArn " : self . arn ,
" CreatedDate " : self . created_date ,
2022-05-19 11:08:02 +00:00
" CompatibleArchitectures " : self . compatible_architectures ,
2021-01-17 15:28:49 +00:00
" CompatibleRuntimes " : self . compatible_runtimes ,
" Description " : self . description ,
" LicenseInfo " : self . license_info ,
}
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_name_type ( ) - > str :
2021-01-17 15:28:49 +00:00
return " LayerVersion "
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_type ( ) - > str :
2021-01-17 15:28:49 +00:00
return " AWS::Lambda::LayerVersion "
@classmethod
2022-10-22 11:40:20 +00:00
def create_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
* * kwargs : Any ,
) - > " LayerVersion " :
2021-01-17 15:28:49 +00:00
properties = cloudformation_json [ " Properties " ]
optional_properties = ( " Description " , " CompatibleRuntimes " , " LicenseInfo " )
# required
spec = {
" Content " : properties [ " Content " ] ,
" LayerName " : resource_name ,
}
for prop in optional_properties :
if prop in properties :
spec [ prop ] = properties [ prop ]
2022-08-13 09:49:43 +00:00
backend = lambda_backends [ account_id ] [ region_name ]
2021-01-17 15:28:49 +00:00
layer_version = backend . publish_layer_version ( spec )
return layer_version
2022-03-17 12:32:31 +00:00
class LambdaAlias ( BaseModel ) :
def __init__ (
2022-08-13 09:49:43 +00:00
self ,
2022-10-22 11:40:20 +00:00
account_id : str ,
region : str ,
name : str ,
function_name : str ,
function_version : str ,
description : str ,
routing_config : str ,
2022-03-17 12:32:31 +00:00
) :
2022-08-13 09:49:43 +00:00
self . arn = (
f " arn:aws:lambda: { region } : { account_id } :function: { function_name } : { name } "
)
2022-03-17 12:32:31 +00:00
self . name = name
self . function_version = function_version
self . description = description
self . routing_config = routing_config
2022-09-28 09:35:12 +00:00
self . revision_id = str ( random . uuid4 ( ) )
2022-03-17 12:32:31 +00:00
2022-10-22 11:40:20 +00:00
def update (
self ,
description : Optional [ str ] ,
function_version : Optional [ str ] ,
routing_config : Optional [ str ] ,
) - > None :
2022-03-17 12:32:31 +00:00
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
2022-10-22 11:40:20 +00:00
def to_json ( self ) - > Dict [ str , Any ] :
2022-03-17 12:32:31 +00:00
return {
" AliasArn " : self . arn ,
" Description " : self . description ,
" FunctionVersion " : self . function_version ,
" Name " : self . name ,
" RevisionId " : self . revision_id ,
" RoutingConfig " : self . routing_config or None ,
}
2021-01-17 15:28:49 +00:00
class Layer ( object ) :
2022-08-13 09:49:43 +00:00
def __init__ ( self , layer_version : LayerVersion ) :
self . region = layer_version . region
self . name = layer_version . name
2021-01-17 15:28:49 +00:00
2022-08-13 09:49:43 +00:00
self . layer_arn = make_layer_arn (
self . region , layer_version . account_id , self . name
)
2021-01-17 15:28:49 +00:00
self . _latest_version = 0
2022-10-22 11:40:20 +00:00
self . layer_versions : Dict [ str , LayerVersion ] = { }
2021-01-17 15:28:49 +00:00
2022-10-22 11:40:20 +00:00
def attach_version ( self , layer_version : LayerVersion ) - > None :
2021-01-17 15:28:49 +00:00
self . _latest_version + = 1
layer_version . attach ( self , self . _latest_version )
self . layer_versions [ str ( self . _latest_version ) ] = layer_version
2022-10-22 11:40:20 +00:00
def delete_version ( self , layer_version : str ) - > None :
2022-03-17 12:32:31 +00:00
self . layer_versions . pop ( str ( layer_version ) , None )
2022-10-22 11:40:20 +00:00
def to_dict ( self ) - > Dict [ str , Any ] :
2022-08-12 16:32:41 +00:00
if not self . layer_versions :
return { }
last_key = sorted ( self . layer_versions . keys ( ) , key = lambda version : int ( version ) ) [
- 1
]
2021-01-17 15:28:49 +00:00
return {
" LayerName " : self . name ,
" LayerArn " : self . layer_arn ,
2022-08-12 16:32:41 +00:00
" LatestMatchingVersion " : self . layer_versions [ last_key ] . get_layer_version ( ) ,
2021-01-17 15:28:49 +00:00
}
2020-11-08 15:16:53 +00:00
class LambdaFunction ( CloudFormationModel , DockerModel ) :
2022-10-22 11:40:20 +00:00
def __init__ (
self ,
account_id : str ,
spec : Dict [ str , Any ] ,
region : str ,
version : Union [ str , int ] = 1 ,
) :
2020-11-08 15:16:53 +00:00
DockerModel . __init__ ( self )
2016-02-12 20:38:39 +00:00
# required
2022-08-13 09:49:43 +00:00
self . account_id = account_id
2017-09-27 23:04:58 +00:00
self . region = region
2016-02-12 20:38:39 +00:00
self . code = spec [ " Code " ]
2016-02-12 19:39:20 +00:00
self . function_name = spec [ " FunctionName " ]
2022-06-10 14:09:13 +00:00
self . handler = spec . get ( " Handler " )
2016-02-12 20:38:39 +00:00
self . role = spec [ " Role " ]
2022-06-10 14:09:13 +00:00
self . run_time = spec . get ( " Runtime " )
2022-08-13 09:49:43 +00:00
self . logs_backend = logs_backends [ account_id ] [ self . region ]
2017-09-27 23:04:58 +00:00
self . environment_vars = spec . get ( " Environment " , { } ) . get ( " Variables " , { } )
2023-11-28 22:01:56 +00:00
self . policy = Policy ( self )
2022-10-22 11:40:20 +00:00
self . url_config : Optional [ FunctionUrlConfig ] = None
2020-01-23 18:46:24 +00:00
self . state = " Active "
2020-08-26 10:06:53 +00:00
self . reserved_concurrency = spec . get ( " ReservedConcurrentExecutions " , None )
2017-09-27 23:04:58 +00:00
2016-02-12 20:38:39 +00:00
# optional
2023-07-06 16:45:29 +00:00
self . ephemeral_storage : str
self . code_digest : str
self . code_bytes : bytes
2023-12-01 23:07:52 +00:00
self . event_invoke_config : List [ EventInvokeConfig ] = [ ]
2023-07-06 16:45:29 +00:00
2016-02-12 20:38:39 +00:00
self . description = spec . get ( " Description " , " " )
self . memory_size = spec . get ( " MemorySize " , 128 )
2023-12-10 20:31:33 +00:00
self . package_type = spec . get ( " PackageType " , " Zip " )
2017-02-24 02:37:43 +00:00
self . publish = spec . get ( " Publish " , False ) # this is ignored currently
2016-02-12 20:38:39 +00:00
self . timeout = spec . get ( " Timeout " , 3 )
2024-01-06 19:20:57 +00:00
self . layers : List [ LayerDataType ] = self . _get_layers_data ( spec . get ( " Layers " , [ ] ) )
2022-02-08 21:12:51 +00:00
self . signing_profile_version_arn = spec . get ( " SigningProfileVersionArn " )
self . signing_job_arn = spec . get ( " SigningJobArn " )
self . code_signing_config_arn = spec . get ( " CodeSigningConfigArn " )
2022-03-17 12:32:31 +00:00
self . tracing_config = spec . get ( " TracingConfig " ) or { " Mode " : " PassThrough " }
2023-07-06 16:45:29 +00:00
self . architectures : List [ str ] = spec . get ( " Architectures " , [ " x86_64 " ] )
self . image_config : ImageConfig = ImageConfig ( spec . get ( " ImageConfig " , { } ) )
_es = spec . get ( " EphemeralStorage " )
if _es :
self . ephemeral_storage = _es [ " Size " ]
else :
self . ephemeral_storage = 512
2016-02-16 20:35:29 +00:00
2022-11-12 13:32:07 +00:00
self . logs_group_name = f " /aws/lambda/ { self . function_name } "
2017-09-27 23:04:58 +00:00
2016-02-16 20:35:29 +00:00
# this isn't finished yet. it needs to find out the VpcId value
2017-02-24 02:37:43 +00:00
self . _vpc_config = spec . get (
" VpcConfig " , { " SubnetIds " : [ ] , " SecurityGroupIds " : [ ] }
)
2016-02-12 19:39:20 +00:00
2016-02-12 20:38:39 +00:00
# auto-generated
2017-11-26 21:28:28 +00:00
self . version = version
2023-09-11 22:23:44 +00:00
self . last_modified = iso_8601_datetime_with_nanoseconds ( )
2017-09-27 23:04:58 +00:00
2023-08-08 10:11:24 +00:00
self . _set_function_code ( self . code )
2017-09-27 23:04:58 +00:00
2023-11-28 22:01:56 +00:00
self . function_arn : str = make_function_arn (
2022-08-13 09:49:43 +00:00
self . region , self . account_id , self . function_name
2019-05-21 16:49:56 +00:00
)
2016-02-12 19:39:20 +00:00
2022-10-22 11:40:20 +00:00
self . tags = spec . get ( " Tags " ) or dict ( )
2017-09-13 19:00:39 +00:00
2022-10-29 13:26:19 +00:00
def __getstate__ ( self ) - > Dict [ str , Any ] :
return {
k : v
for ( k , v ) in self . __dict__ . items ( )
if k != " _DockerModel__docker_client "
}
2022-10-22 11:40:20 +00:00
def set_version ( self , version : int ) - > None :
2019-05-21 16:49:56 +00:00
self . function_arn = make_function_ver_arn (
2022-08-13 09:49:43 +00:00
self . region , self . account_id , self . function_name , version
2019-05-21 16:49:56 +00:00
)
2017-11-26 21:28:28 +00:00
self . version = version
2023-09-11 22:23:44 +00:00
self . last_modified = iso_8601_datetime_with_nanoseconds ( )
2017-11-26 21:28:28 +00:00
2023-07-06 16:45:29 +00:00
@property
def architectures ( self ) - > List [ str ] :
return self . _architectures
@architectures.setter
def architectures ( self , architectures : List [ str ] ) - > None :
if (
len ( architectures ) > 1
or not architectures
or architectures [ 0 ] not in ( " x86_64 " , " arm64 " )
) :
raise ValidationException (
str ( architectures ) ,
" architectures " ,
" Member must satisfy constraint: "
" [Member must satisfy enum value set: [x86_64, arm64], Member must not be null] " ,
)
self . _architectures = architectures
@property
def ephemeral_storage ( self ) - > int :
return self . _ephemeral_storage
@ephemeral_storage.setter
def ephemeral_storage ( self , ephemeral_storage : int ) - > None :
if ephemeral_storage > 10240 :
raise ValidationException (
str ( ephemeral_storage ) ,
" ephemeralStorage.size " ,
" Member must have value less than or equal to 10240 " ,
)
# ephemeral_storage < 512 is handled by botocore 1.30.0
self . _ephemeral_storage = ephemeral_storage
2016-02-16 20:35:29 +00:00
@property
2022-10-22 11:40:20 +00:00
def vpc_config ( self ) - > Dict [ str , Any ] : # type: ignore[misc]
2016-02-16 20:35:29 +00:00
config = self . _vpc_config . copy ( )
if config [ " SecurityGroupIds " ] :
config . update ( { " VpcId " : " vpc-123abc " } )
return config
2019-04-16 03:07:14 +00:00
@property
2022-10-22 11:40:20 +00:00
def physical_resource_id ( self ) - > str :
2019-04-16 03:07:14 +00:00
return self . function_name
2022-10-22 11:40:20 +00:00
def __repr__ ( self ) - > str :
2016-02-12 19:39:20 +00:00
return json . dumps ( self . get_configuration ( ) )
2024-01-06 19:20:57 +00:00
def _get_layers_data ( self , layers_versions_arns : List [ str ] ) - > List [ LayerDataType ] :
2022-08-13 09:49:43 +00:00
backend = lambda_backends [ self . account_id ] [ self . region ]
2021-01-17 15:28:49 +00:00
layer_versions = [
backend . layers_versions_by_arn ( layer_version )
for layer_version in layers_versions_arns
]
if not all ( layer_versions ) :
2023-06-15 11:05:06 +00:00
raise UnknownLayerVersionException ( layers_versions_arns )
2023-11-28 22:01:56 +00:00
# The `if lv` part is not necessary - we know there are no None's, because of the `all()`-check earlier
# But MyPy does not seem to understand this
2024-01-06 19:20:57 +00:00
return [
{ " Arn " : lv . arn , " CodeSize " : lv . code_size } for lv in layer_versions if lv
]
2021-01-17 15:28:49 +00:00
2022-10-22 11:40:20 +00:00
def get_code_signing_config ( self ) - > Dict [ str , Any ] :
2022-02-08 21:12:51 +00:00
return {
" CodeSigningConfigArn " : self . code_signing_config_arn ,
" FunctionName " : self . function_name ,
}
2022-10-22 11:40:20 +00:00
def get_configuration ( self , on_create : bool = False ) - > Dict [ str , Any ] :
2017-09-27 23:04:58 +00:00
config = {
2016-02-12 19:39:20 +00:00
" CodeSha256 " : self . code_sha_256 ,
" CodeSize " : self . code_size ,
" Description " : self . description ,
" FunctionArn " : self . function_arn ,
" FunctionName " : self . function_name ,
" Handler " : self . handler ,
" LastModified " : self . last_modified ,
" MemorySize " : self . memory_size ,
" Role " : self . role ,
" Runtime " : self . run_time ,
2020-01-23 18:46:24 +00:00
" State " : self . state ,
2022-02-08 21:12:51 +00:00
" PackageType " : self . package_type ,
2016-02-12 19:39:20 +00:00
" Timeout " : self . timeout ,
2017-11-26 21:28:28 +00:00
" Version " : str ( self . version ) ,
2016-02-12 19:39:20 +00:00
" VpcConfig " : self . vpc_config ,
2021-01-17 15:28:49 +00:00
" Layers " : self . layers ,
2022-02-08 21:12:51 +00:00
" SigningProfileVersionArn " : self . signing_profile_version_arn ,
" SigningJobArn " : self . signing_job_arn ,
2022-03-17 12:32:31 +00:00
" TracingConfig " : self . tracing_config ,
2023-07-06 16:45:29 +00:00
" Architectures " : self . architectures ,
" EphemeralStorage " : {
" Size " : self . ephemeral_storage ,
} ,
" SnapStart " : { " ApplyOn " : " None " , " OptimizationStatus " : " Off " } ,
2016-02-12 19:39:20 +00:00
}
2023-07-06 16:45:29 +00:00
if self . package_type == " Image " :
config [ " ImageConfigResponse " ] = {
" ImageConfig " : self . image_config . response ( ) ,
}
2022-03-17 12:32:31 +00:00
if not on_create :
# Only return this variable after the first creation
config [ " LastUpdateStatus " ] = " Successful "
2017-09-27 23:04:58 +00:00
if self . environment_vars :
config [ " Environment " ] = { " Variables " : self . environment_vars }
2016-02-12 19:39:20 +00:00
2017-09-27 23:04:58 +00:00
return config
2022-10-22 11:40:20 +00:00
def get_code ( self ) - > Dict [ str , Any ] :
2022-06-10 14:09:13 +00:00
resp = { " Configuration " : self . get_configuration ( ) }
if " S3Key " in self . code :
resp [ " Code " ] = {
2022-11-12 13:32:07 +00:00
" Location " : f " s3://awslambda- { self . region } -tasks.s3- { self . region } .amazonaws.com/ { self . code [ ' S3Key ' ] } " ,
2017-09-27 23:04:58 +00:00
" RepositoryType " : " S3 " ,
2022-06-10 14:09:13 +00:00
}
elif " ImageUri " in self . code :
resp [ " Code " ] = {
" RepositoryType " : " ECR " ,
" ImageUri " : self . code . get ( " ImageUri " ) ,
" ResolvedImageUri " : self . code . get ( " ImageUri " ) . split ( " : " ) [ 0 ]
+ " @sha256: "
+ self . code_sha_256 ,
}
2022-03-17 12:32:31 +00:00
if self . tags :
2022-06-10 14:09:13 +00:00
resp [ " Tags " ] = self . tags
2020-08-26 10:06:53 +00:00
if self . reserved_concurrency :
2022-06-10 14:09:13 +00:00
resp . update (
2020-08-26 10:06:53 +00:00
{
" Concurrency " : {
" ReservedConcurrentExecutions " : self . reserved_concurrency
}
}
)
2022-06-10 14:09:13 +00:00
return resp
2017-09-27 23:04:58 +00:00
2022-10-22 11:40:20 +00:00
def update_configuration ( self , config_updates : Dict [ str , Any ] ) - > Dict [ str , Any ] :
2019-10-08 20:59:03 +00:00
for key , value in config_updates . items ( ) :
if key == " Description " :
self . description = value
elif key == " Handler " :
self . handler = value
elif key == " MemorySize " :
self . memory_size = value
elif key == " Role " :
self . role = value
elif key == " Runtime " :
self . run_time = value
elif key == " Timeout " :
self . timeout = value
elif key == " VpcConfig " :
2020-11-18 08:45:31 +00:00
self . _vpc_config = value
2019-11-04 13:44:01 +00:00
elif key == " Environment " :
self . environment_vars = value [ " Variables " ]
2021-01-17 15:28:49 +00:00
elif key == " Layers " :
self . layers = self . _get_layers_data ( value )
2019-10-08 20:59:03 +00:00
return self . get_configuration ( )
2023-08-08 10:11:24 +00:00
def _set_function_code ( self , updated_spec : Dict [ str , Any ] ) - > None :
from_update = updated_spec is not self . code
# "DryRun" is only used for UpdateFunctionCode
if from_update and " DryRun " in updated_spec and updated_spec [ " DryRun " ] :
return
2019-10-08 20:59:03 +00:00
2019-10-09 20:15:10 +00:00
if " ZipFile " in updated_spec :
2023-08-08 10:11:24 +00:00
if from_update :
self . code [ " ZipFile " ] = updated_spec [ " ZipFile " ]
2019-10-09 20:15:10 +00:00
2022-03-17 12:32:31 +00:00
(
self . code_bytes ,
self . code_size ,
self . code_sha_256 ,
self . code_digest ,
) = _zipfile_content ( updated_spec [ " ZipFile " ] )
2019-10-08 20:59:03 +00:00
# TODO: we should be putting this in a lambda bucket
2022-09-28 09:35:12 +00:00
self . code [ " UUID " ] = str ( random . uuid4 ( ) )
2022-11-12 13:32:07 +00:00
self . code [ " S3Key " ] = f " { self . function_name } - { self . code [ ' UUID ' ] } "
2019-10-09 20:15:10 +00:00
elif " S3Bucket " in updated_spec and " S3Key " in updated_spec :
2019-10-08 20:59:03 +00:00
key = None
try :
2023-08-08 10:11:24 +00:00
if from_update :
# FIXME: does not validate bucket region
key = s3_backends [ self . account_id ] [ " global " ] . get_object (
updated_spec [ " S3Bucket " ] , updated_spec [ " S3Key " ]
)
else :
key = _validate_s3_bucket_and_key ( self . account_id , data = self . code )
2019-10-08 20:59:03 +00:00
except MissingBucket :
if do_validate_s3 ( ) :
raise ValueError (
" InvalidParameterValueException " ,
" Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist " ,
)
except MissingKey :
if do_validate_s3 ( ) :
raise ValueError (
" InvalidParameterValueException " ,
" Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist. " ,
)
if key :
2022-03-17 12:32:31 +00:00
(
self . code_bytes ,
self . code_size ,
self . code_sha_256 ,
self . code_digest ,
2023-07-20 15:46:54 +00:00
) = _s3_content ( key )
2023-08-08 10:11:24 +00:00
else :
self . code_bytes = b " "
self . code_size = 0
self . code_sha_256 = " "
if from_update :
2019-11-24 14:54:38 +00:00
self . code [ " S3Bucket " ] = updated_spec [ " S3Bucket " ]
self . code [ " S3Key " ] = updated_spec [ " S3Key " ]
2023-08-08 10:11:24 +00:00
elif " ImageUri " in updated_spec :
if settings . lambda_stub_ecr ( ) :
self . code_sha_256 = hashlib . sha256 (
updated_spec [ " ImageUri " ] . encode ( " utf-8 " )
) . hexdigest ( )
self . code_size = 0
else :
if " @ " in updated_spec [ " ImageUri " ] :
# deploying via digest
uri , digest = updated_spec [ " ImageUri " ] . split ( " @ " )
image_id = { " imageDigest " : digest }
else :
# deploying via tag
uri , tag = updated_spec [ " ImageUri " ] . split ( " : " )
image_id = { " imageTag " : tag }
repo_name = uri . split ( " / " ) [ - 1 ]
ecr_backend = ecr_backends [ self . account_id ] [ self . region ]
registry_id = ecr_backend . describe_registry ( ) [ " registryId " ]
images = ecr_backend . batch_get_image (
repository_name = repo_name , image_ids = [ image_id ]
) [ " images " ]
2019-10-08 20:59:03 +00:00
2023-08-08 10:11:24 +00:00
if len ( images ) == 0 :
raise ImageNotFoundException ( image_id , repo_name , registry_id ) # type: ignore
else :
manifest = json . loads ( images [ 0 ] [ " imageManifest " ] )
self . code_sha_256 = images [ 0 ] [ " imageId " ] [ " imageDigest " ] . replace (
" sha256: " , " "
)
self . code_size = manifest [ " config " ] [ " size " ]
if from_update :
self . code [ " ImageUri " ] = updated_spec [ " ImageUri " ]
def update_function_code ( self , updated_spec : Dict [ str , Any ] ) - > Dict [ str , Any ] :
self . _set_function_code ( updated_spec )
2019-10-08 20:59:03 +00:00
return self . get_configuration ( )
2017-09-27 23:04:58 +00:00
@staticmethod
2022-10-22 11:40:20 +00:00
def convert ( s : Any ) - > str : # type: ignore[misc]
2016-10-09 15:13:52 +00:00
try :
2016-12-03 23:17:15 +00:00
return str ( s , encoding = " utf-8 " )
2017-12-08 21:02:34 +00:00
except Exception :
2016-10-09 15:13:52 +00:00
return s
2022-10-22 11:40:20 +00:00
def _invoke_lambda ( self , event : Optional [ str ] = None ) - > Tuple [ str , bool , str ] :
2023-11-07 22:48:26 +00:00
import docker
import docker . errors
2021-02-15 10:31:33 +00:00
# Create the LogGroup if necessary, to write the result to
2023-08-27 18:14:51 +00:00
self . logs_backend . ensure_log_group ( self . logs_group_name )
2017-09-27 23:04:58 +00:00
# TODO: context not yet implemented
if event is None :
2022-10-22 11:40:20 +00:00
event = dict ( ) # type: ignore[assignment]
2020-05-07 09:40:24 +00:00
output = None
2016-10-06 14:41:31 +00:00
2016-10-06 09:52:23 +00:00
try :
2017-09-27 23:04:58 +00:00
# TODO: I believe we can keep the container running and feed events as needed
# also need to hook it up to the other services so it can make kws/s3 etc calls
2020-01-05 11:36:51 +00:00
# Should get invoke_id /RequestId from invocation
2017-09-27 23:04:58 +00:00
env_vars = {
2020-08-19 21:18:32 +00:00
" _HANDLER " : self . handler ,
2022-11-12 13:32:07 +00:00
" AWS_EXECUTION_ENV " : f " AWS_Lambda_ { self . run_time } " ,
2017-09-27 23:04:58 +00:00
" AWS_LAMBDA_FUNCTION_TIMEOUT " : self . timeout ,
" AWS_LAMBDA_FUNCTION_NAME " : self . function_name ,
" AWS_LAMBDA_FUNCTION_MEMORY_SIZE " : self . memory_size ,
" AWS_LAMBDA_FUNCTION_VERSION " : self . version ,
" AWS_REGION " : self . region ,
2020-08-19 21:18:32 +00:00
" AWS_ACCESS_KEY_ID " : " role-account-id " ,
" AWS_SECRET_ACCESS_KEY " : " role-secret-key " ,
" AWS_SESSION_TOKEN " : " session-token " ,
2017-09-27 23:04:58 +00:00
}
env_vars . update ( self . environment_vars )
2022-01-27 12:04:03 +00:00
env_vars [ " MOTO_HOST " ] = settings . moto_server_host ( )
2023-09-27 18:34:30 +00:00
moto_port = settings . moto_server_port ( )
env_vars [ " MOTO_PORT " ] = moto_port
env_vars [ " MOTO_HTTP_ENDPOINT " ] = f ' { env_vars [ " MOTO_HOST " ] } : { moto_port } '
2024-01-17 10:05:37 +00:00
if settings . is_test_proxy_mode ( ) :
2023-09-27 18:34:30 +00:00
env_vars [ " HTTPS_PROXY " ] = env_vars [ " MOTO_HTTP_ENDPOINT " ]
env_vars [ " AWS_CA_BUNDLE " ] = " /var/task/ca.crt "
2017-09-27 23:04:58 +00:00
2020-05-07 09:40:24 +00:00
container = exit_code = None
2020-01-23 00:58:25 +00:00
log_config = docker . types . LogConfig ( type = docker . types . LogConfig . types . JSON )
2021-08-28 09:48:28 +00:00
2023-09-05 17:31:13 +00:00
with _DockerDataVolumeContext (
self
) as data_vol , _DockerDataVolumeLayerContext ( self ) as layer_context :
2017-09-27 23:04:58 +00:00
try :
2022-10-22 11:40:20 +00:00
run_kwargs : Dict [ str , Any ] = dict ( )
2022-01-27 12:04:03 +00:00
network_name = settings . moto_network_name ( )
network_mode = settings . moto_network_mode ( )
if network_name :
run_kwargs [ " network " ] = network_name
elif network_mode :
run_kwargs [ " network_mode " ] = network_mode
elif settings . TEST_SERVER_MODE :
# AWSLambda can make HTTP requests to a Docker container called 'motoserver'
# Only works if our Docker-container is named 'motoserver'
# TODO: should remove this and rely on 'network_mode' instead, as this is too tightly coupled with our own test setup
run_kwargs [ " links " ] = { " motoserver " : " motoserver " }
2021-10-13 21:35:38 +00:00
# add host.docker.internal host on linux to emulate Mac + Windows behavior
# for communication with other mock AWS services running on localhost
if platform == " linux " or platform == " linux2 " :
run_kwargs [ " extra_hosts " ] = {
" host.docker.internal " : " host-gateway "
}
2024-02-15 20:07:25 +00:00
# Change entry point if requested
if self . image_config . entry_point :
run_kwargs [ " entrypoint " ] = self . image_config . entry_point
2023-02-02 12:56:50 +00:00
# The requested image can be found in one of a few repos:
# - User-provided repo
# - mlupin/docker-lambda (the repo with up-to-date AWSLambda images
# - lambci/lambda (the repo with older/outdated AWSLambda images
#
# We'll cycle through all of them - when we find the repo that contains our image, we use it
image_repos = set (
[
settings . moto_lambda_image ( ) ,
" mlupin/docker-lambda " ,
" lambci/lambda " ,
]
)
for image_repo in image_repos :
image_ref = f " { image_repo } : { self . run_time } "
try :
2023-04-13 09:25:47 +00:00
self . ensure_image_exists ( image_ref )
2023-02-02 12:56:50 +00:00
break
except docker . errors . NotFound :
pass
2023-09-05 17:31:13 +00:00
volumes = {
data_vol . name : { " bind " : " /var/task " , " mode " : " rw " } ,
layer_context . name : { " bind " : " /opt " , " mode " : " rw " } ,
}
2017-09-27 23:04:58 +00:00
container = self . docker_client . containers . run (
2021-02-18 08:58:20 +00:00
image_ref ,
2017-09-27 23:04:58 +00:00
[ self . handler , json . dumps ( event ) ] ,
remove = False ,
2022-11-12 13:32:07 +00:00
mem_limit = f " { self . memory_size } m " ,
2023-09-05 17:31:13 +00:00
volumes = volumes ,
2017-09-27 23:04:58 +00:00
environment = env_vars ,
detach = True ,
2020-01-23 00:58:25 +00:00
log_config = log_config ,
2022-01-27 12:04:03 +00:00
* * run_kwargs ,
2017-09-27 23:04:58 +00:00
)
finally :
if container :
2017-10-25 19:04:00 +00:00
try :
2023-03-18 10:35:48 +00:00
exit_code = container . wait ( timeout = 300 ) [ " StatusCode " ]
2017-10-25 19:04:00 +00:00
except requests . exceptions . ReadTimeout :
exit_code = - 1
container . stop ( )
container . kill ( )
2018-06-01 02:39:22 +00:00
2017-09-27 23:04:58 +00:00
output = container . logs ( stdout = False , stderr = True )
output + = container . logs ( stdout = True , stderr = False )
container . remove ( )
2022-10-22 11:40:20 +00:00
output = output . decode ( " utf-8 " ) # type: ignore[union-attr]
2017-09-27 23:04:58 +00:00
2021-08-28 09:48:28 +00:00
self . save_logs ( output )
2017-09-27 23:04:58 +00:00
2020-01-05 11:36:51 +00:00
# We only care about the response from the lambda
# Which is the last line of the output, according to https://github.com/lambci/docker-lambda/issues/25
2020-05-07 09:40:24 +00:00
resp = output . splitlines ( ) [ - 1 ]
logs = os . linesep . join (
[ line for line in self . convert ( output ) . splitlines ( ) [ : - 1 ] ]
)
2021-08-28 08:41:05 +00:00
invocation_error = exit_code != 0
return resp , invocation_error , logs
2020-07-20 10:37:45 +00:00
except docker . errors . DockerException as e :
# Docker itself is probably not running - there will be no Lambda-logs to handle
2022-11-12 13:32:07 +00:00
msg = f " error running docker: { e } "
2023-03-12 16:54:50 +00:00
logger . error ( msg )
2021-08-28 09:48:28 +00:00
self . save_logs ( msg )
return msg , True , " "
2022-10-22 11:40:20 +00:00
def save_logs ( self , output : str ) - > None :
2021-08-28 09:48:28 +00:00
# Send output to "logs" backend
2022-09-28 09:35:12 +00:00
invoke_id = random . uuid4 ( ) . hex
2023-09-11 22:23:44 +00:00
date = utcnow ( )
2021-08-28 09:48:28 +00:00
log_stream_name = (
2022-11-12 13:32:07 +00:00
f " { date . year } / { date . month : 02d } / { date . day : 02d } /[ { self . version } ] { invoke_id } "
2021-08-28 09:48:28 +00:00
)
self . logs_backend . create_log_stream ( self . logs_group_name , log_stream_name )
log_events = [
{ " timestamp " : unix_time_millis ( ) , " message " : line }
for line in output . splitlines ( )
]
self . logs_backend . put_log_events (
2022-03-11 21:28:45 +00:00
self . logs_group_name , log_stream_name , log_events
2021-08-28 09:48:28 +00:00
)
2016-10-06 09:52:23 +00:00
2022-10-22 11:40:20 +00:00
def invoke (
self , body : str , request_headers : Any , response_headers : Any
) - > Union [ str , bytes ] :
2017-09-27 23:04:58 +00:00
if body :
body = json . loads ( body )
2021-11-03 21:00:42 +00:00
else :
body = " {} "
2017-09-27 23:04:58 +00:00
2016-06-22 19:24:46 +00:00
# Get the invocation type:
2022-03-11 21:28:45 +00:00
res , errored , logs = self . _invoke_lambda ( event = body )
2022-10-22 11:40:20 +00:00
if errored :
response_headers [ " x-amz-function-error " ] = " Handled "
2021-01-26 13:28:01 +00:00
inv_type = request_headers . get ( " x-amz-invocation-type " , " RequestResponse " )
if inv_type == " RequestResponse " :
2020-05-07 09:40:24 +00:00
encoded = base64 . b64encode ( logs . encode ( " utf-8 " ) )
2017-02-24 00:43:48 +00:00
response_headers [ " x-amz-log-result " ] = encoded . decode ( " utf-8 " )
2022-10-22 11:40:20 +00:00
return res . encode ( " utf-8 " )
2016-12-03 23:17:15 +00:00
else :
2022-10-22 11:40:20 +00:00
return res
2016-06-22 19:24:46 +00:00
2020-08-01 14:23:36 +00:00
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_name_type ( ) - > str :
2020-08-01 14:23:36 +00:00
return " FunctionName "
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_type ( ) - > str :
2020-08-01 14:23:36 +00:00
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
return " AWS::Lambda::Function "
2016-02-12 20:38:39 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def create_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
* * kwargs : Any ,
) - > " LambdaFunction " :
2016-02-12 20:38:39 +00:00
properties = cloudformation_json [ " Properties " ]
2020-08-26 10:06:53 +00:00
optional_properties = (
" Description " ,
" MemorySize " ,
" Publish " ,
" Timeout " ,
" VpcConfig " ,
" Environment " ,
" ReservedConcurrentExecutions " ,
)
2016-02-12 20:38:39 +00:00
2016-02-16 22:07:35 +00:00
# required
spec = {
2016-02-12 20:38:39 +00:00
" Code " : properties [ " Code " ] ,
" FunctionName " : resource_name ,
" Handler " : properties [ " Handler " ] ,
" Role " : properties [ " Role " ] ,
" Runtime " : properties [ " Runtime " ] ,
2016-02-16 22:07:35 +00:00
}
2020-08-26 10:06:53 +00:00
2017-02-24 02:37:43 +00:00
# NOTE: Not doing `properties.get(k, DEFAULT)` to avoid duplicating the
# default logic
2016-02-16 22:07:35 +00:00
for prop in optional_properties :
if prop in properties :
spec [ prop ] = properties [ prop ]
2016-02-12 20:38:39 +00:00
2017-05-17 22:16:40 +00:00
# when ZipFile is present in CloudFormation, per the official docs,
# the code it's a plaintext code snippet up to 4096 bytes.
# this snippet converts this plaintext code to a proper base64-encoded ZIP file.
if " ZipFile " in properties [ " Code " ] :
spec [ " Code " ] [ " ZipFile " ] = base64 . b64encode (
2017-09-27 23:04:58 +00:00
cls . _create_zipfile_from_plaintext_code ( spec [ " Code " ] [ " ZipFile " ] )
)
2017-05-17 22:16:40 +00:00
2022-08-13 09:49:43 +00:00
backend = lambda_backends [ account_id ] [ region_name ]
2016-02-16 22:07:35 +00:00
fn = backend . create_function ( spec )
2016-02-12 20:38:39 +00:00
return fn
2021-11-03 21:00:42 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def has_cfn_attr ( cls , attr : str ) - > bool :
2022-03-11 21:28:45 +00:00
return attr in [ " Arn " ]
2021-11-03 21:00:42 +00:00
2022-10-22 11:40:20 +00:00
def get_cfn_attribute ( self , attribute_name : str ) - > str :
2017-09-27 23:04:58 +00:00
from moto . cloudformation . exceptions import UnformattedGetAttTemplateException
2019-10-31 15:44:26 +00:00
2017-05-24 12:54:00 +00:00
if attribute_name == " Arn " :
2022-08-13 09:49:43 +00:00
return make_function_arn ( self . region , self . account_id , self . function_name )
2017-05-24 12:54:00 +00:00
raise UnformattedGetAttTemplateException ( )
2019-11-24 14:54:38 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def update_from_cloudformation_json ( # type: ignore[misc]
2022-08-13 09:49:43 +00:00
cls ,
2022-10-22 11:40:20 +00:00
original_resource : " LambdaFunction " ,
new_resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
) - > " LambdaFunction " :
2019-11-24 14:54:38 +00:00
updated_props = cloudformation_json [ " Properties " ]
original_resource . update_configuration ( updated_props )
original_resource . update_function_code ( updated_props [ " Code " ] )
return original_resource
2017-05-17 22:16:40 +00:00
@staticmethod
2022-10-22 11:40:20 +00:00
def _create_zipfile_from_plaintext_code ( code : str ) - > bytes :
2017-05-17 22:16:40 +00:00
zip_output = io . BytesIO ( )
zip_file = zipfile . ZipFile ( zip_output , " w " , zipfile . ZIP_DEFLATED )
2021-11-03 21:00:42 +00:00
zip_file . writestr ( " index.py " , code )
# This should really be part of the 'lambci' docker image
from moto . packages . cfnresponse import cfnresponse
with open ( cfnresponse . __file__ ) as cfn :
zip_file . writestr ( " cfnresponse.py " , cfn . read ( ) )
2017-05-17 22:16:40 +00:00
zip_file . close ( )
zip_output . seek ( 0 )
return zip_output . read ( )
2022-10-22 11:40:20 +00:00
def delete ( self , account_id : str , region : str ) - > None :
2022-08-13 09:49:43 +00:00
lambda_backends [ account_id ] [ region ] . delete_function ( self . function_name )
2019-11-24 14:54:38 +00:00
2022-10-22 11:40:20 +00:00
def create_url_config ( self , config : Dict [ str , Any ] ) - > " FunctionUrlConfig " :
2022-08-24 21:19:17 +00:00
self . url_config = FunctionUrlConfig ( function = self , config = config )
2023-07-20 15:46:54 +00:00
return self . url_config
2022-08-24 21:19:17 +00:00
2022-10-22 11:40:20 +00:00
def delete_url_config ( self ) - > None :
2022-08-24 21:19:17 +00:00
self . url_config = None
2022-10-22 11:40:20 +00:00
def get_url_config ( self ) - > " FunctionUrlConfig " :
2022-08-24 21:19:17 +00:00
if not self . url_config :
2024-03-19 21:49:46 +00:00
raise GenericResourcNotFound ( )
2022-08-24 21:19:17 +00:00
return self . url_config
2022-10-22 11:40:20 +00:00
def update_url_config ( self , config : Dict [ str , Any ] ) - > " FunctionUrlConfig " :
self . url_config . update ( config ) # type: ignore[union-attr]
return self . url_config # type: ignore[return-value]
2022-08-24 21:19:17 +00:00
class FunctionUrlConfig :
2022-10-22 11:40:20 +00:00
def __init__ ( self , function : LambdaFunction , config : Dict [ str , Any ] ) :
2022-08-24 21:19:17 +00:00
self . function = function
self . config = config
2022-09-28 09:35:12 +00:00
self . url = f " https:// { random . uuid4 ( ) . hex } .lambda-url. { function . region } .on.aws "
2023-09-11 22:23:44 +00:00
self . created = utcnow ( ) . strftime ( " % Y- % m- %d T % H: % M: % S.000+0000 " )
2022-08-24 21:19:17 +00:00
self . last_modified = self . created
2022-10-22 11:40:20 +00:00
def to_dict ( self ) - > Dict [ str , Any ] :
2022-08-24 21:19:17 +00:00
return {
" FunctionUrl " : self . url ,
" FunctionArn " : self . function . function_arn ,
" AuthType " : self . config . get ( " AuthType " ) ,
" Cors " : self . config . get ( " Cors " ) ,
" CreationTime " : self . created ,
" LastModifiedTime " : self . last_modified ,
2023-08-21 20:33:16 +00:00
" InvokeMode " : self . config . get ( " InvokeMode " ) or " Buffered " ,
2022-08-24 21:19:17 +00:00
}
2022-10-22 11:40:20 +00:00
def update ( self , new_config : Dict [ str , Any ] ) - > None :
2022-08-24 21:19:17 +00:00
if new_config . get ( " Cors " ) :
self . config [ " Cors " ] = new_config [ " Cors " ]
if new_config . get ( " AuthType " ) :
self . config [ " AuthType " ] = new_config [ " AuthType " ]
2023-09-11 22:23:44 +00:00
self . last_modified = utcnow ( ) . strftime ( " % Y- % m- %d T % H: % M: % S " )
2022-08-24 21:19:17 +00:00
2016-02-12 19:39:20 +00:00
2020-08-01 14:23:36 +00:00
class EventSourceMapping ( CloudFormationModel ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self , spec : Dict [ str , Any ] ) :
2017-05-24 12:54:00 +00:00
# required
2020-06-14 15:03:00 +00:00
self . function_name = spec [ " FunctionName " ]
2017-05-24 12:54:00 +00:00
self . event_source_arn = spec [ " EventSourceArn " ]
2020-06-14 15:03:00 +00:00
# optional
2022-10-22 11:40:20 +00:00
self . batch_size = spec . get ( " BatchSize " ) # type: ignore[assignment]
2020-06-14 15:03:00 +00:00
self . starting_position = spec . get ( " StartingPosition " , " TRIM_HORIZON " )
self . enabled = spec . get ( " Enabled " , True )
self . starting_position_timestamp = spec . get ( " StartingPositionTimestamp " , None )
2024-01-30 20:51:15 +00:00
self . function_arn : str = spec [ " FunctionArn " ]
2022-09-28 09:35:12 +00:00
self . uuid = str ( random . uuid4 ( ) )
2023-09-11 22:23:44 +00:00
self . last_modified = time . mktime ( utcnow ( ) . timetuple ( ) )
2019-08-21 21:45:37 +00:00
2022-10-22 11:40:20 +00:00
def _get_service_source_from_arn ( self , event_source_arn : str ) - > str :
2020-06-14 15:03:00 +00:00
return event_source_arn . split ( " : " ) [ 2 ] . lower ( )
2022-10-22 11:40:20 +00:00
def _validate_event_source ( self , event_source_arn : str ) - > bool :
2020-06-14 15:03:00 +00:00
valid_services = ( " dynamodb " , " kinesis " , " sqs " )
service = self . _get_service_source_from_arn ( event_source_arn )
2022-10-22 11:40:20 +00:00
return service in valid_services
2020-06-14 15:03:00 +00:00
@property
2022-10-22 11:40:20 +00:00
def event_source_arn ( self ) - > str :
2020-06-14 15:03:00 +00:00
return self . _event_source_arn
@event_source_arn.setter
2022-10-22 11:40:20 +00:00
def event_source_arn ( self , event_source_arn : str ) - > None :
2020-06-14 15:03:00 +00:00
if not self . _validate_event_source ( event_source_arn ) :
raise ValueError (
" InvalidParameterValueException " , " Unsupported event source type "
)
self . _event_source_arn = event_source_arn
@property
2022-10-22 11:40:20 +00:00
def batch_size ( self ) - > int :
2020-06-14 15:03:00 +00:00
return self . _batch_size
@batch_size.setter
2022-10-22 11:40:20 +00:00
def batch_size ( self , batch_size : Optional [ int ] ) - > None :
2020-06-14 15:03:00 +00:00
batch_size_service_map = {
2019-08-21 21:45:37 +00:00
" kinesis " : ( 100 , 10000 ) ,
" dynamodb " : ( 100 , 1000 ) ,
" sqs " : ( 10 , 10 ) ,
}
2020-06-14 15:03:00 +00:00
source_type = self . _get_service_source_from_arn ( self . event_source_arn )
batch_size_for_source = batch_size_service_map [ source_type ]
if batch_size is None :
self . _batch_size = batch_size_for_source [ 0 ]
elif batch_size > batch_size_for_source [ 1 ] :
2022-11-12 13:32:07 +00:00
error_message = (
f " BatchSize { batch_size } exceeds the max of { batch_size_for_source [ 1 ] } "
2020-06-14 15:03:00 +00:00
)
raise ValueError ( " InvalidParameterValueException " , error_message )
else :
self . _batch_size = int ( batch_size )
2017-05-24 12:54:00 +00:00
2022-10-22 11:40:20 +00:00
def get_configuration ( self ) - > Dict [ str , Any ] :
2019-08-21 01:54:57 +00:00
return {
" UUID " : self . uuid ,
" BatchSize " : self . batch_size ,
" EventSourceArn " : self . event_source_arn ,
" FunctionArn " : self . function_arn ,
" LastModified " : self . last_modified ,
2024-01-30 20:51:15 +00:00
" LastProcessingResult " : None ,
2019-08-21 01:54:57 +00:00
" State " : " Enabled " if self . enabled else " Disabled " ,
" StateTransitionReason " : " User initiated " ,
2024-01-30 20:51:15 +00:00
" StartingPosition " : self . starting_position ,
2019-08-21 01:54:57 +00:00
}
2022-10-22 11:40:20 +00:00
def delete ( self , account_id : str , region_name : str ) - > None :
2022-08-13 09:49:43 +00:00
lambda_backend = lambda_backends [ account_id ] [ region_name ]
2020-06-14 15:03:00 +00:00
lambda_backend . delete_event_source_mapping ( self . uuid )
2020-08-01 14:23:36 +00:00
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_type ( ) - > str :
2020-08-01 14:23:36 +00:00
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html
return " AWS::Lambda::EventSourceMapping "
2017-05-24 12:54:00 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def create_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
* * kwargs : Any ,
) - > " EventSourceMapping " :
2017-05-24 12:54:00 +00:00
properties = cloudformation_json [ " Properties " ]
2022-08-13 09:49:43 +00:00
lambda_backend = lambda_backends [ account_id ] [ region_name ]
2020-06-14 15:03:00 +00:00
return lambda_backend . create_event_source_mapping ( properties )
@classmethod
2022-10-22 11:40:20 +00:00
def update_from_cloudformation_json ( # type: ignore[misc]
2022-08-13 09:49:43 +00:00
cls ,
2022-10-22 11:40:20 +00:00
original_resource : Any ,
new_resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
) - > " EventSourceMapping " :
2020-06-14 15:03:00 +00:00
properties = cloudformation_json [ " Properties " ]
event_source_uuid = original_resource . uuid
2022-08-13 09:49:43 +00:00
lambda_backend = lambda_backends [ account_id ] [ region_name ]
2023-12-01 11:51:28 +00:00
return lambda_backend . update_event_source_mapping ( event_source_uuid , properties ) # type: ignore[return-value]
2020-06-14 15:03:00 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def delete_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
) - > None :
2020-06-14 15:03:00 +00:00
properties = cloudformation_json [ " Properties " ]
2022-08-13 09:49:43 +00:00
lambda_backend = lambda_backends [ account_id ] [ region_name ]
2020-06-14 15:03:00 +00:00
esms = lambda_backend . list_event_source_mappings (
event_source_arn = properties [ " EventSourceArn " ] ,
function_name = properties [ " FunctionName " ] ,
)
for esm in esms :
2020-08-27 09:11:47 +00:00
if esm . uuid == resource_name :
2022-08-13 09:49:43 +00:00
esm . delete ( account_id , region_name )
2017-05-24 12:54:00 +00:00
2020-08-27 09:11:47 +00:00
@property
2022-10-22 11:40:20 +00:00
def physical_resource_id ( self ) - > str :
2020-08-27 09:11:47 +00:00
return self . uuid
2017-05-24 12:54:00 +00:00
2020-08-01 14:23:36 +00:00
class LambdaVersion ( CloudFormationModel ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self , spec : Dict [ str , Any ] ) :
2017-05-24 12:54:00 +00:00
self . version = spec [ " Version " ]
2022-10-22 11:40:20 +00:00
def __repr__ ( self ) - > str :
return str ( self . logical_resource_id ) # type: ignore[attr-defined]
2018-05-15 18:13:01 +00:00
2020-08-01 14:23:36 +00:00
@staticmethod
2022-10-22 11:40:20 +00:00
def cloudformation_type ( ) - > str :
2020-08-01 14:23:36 +00:00
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-version.html
return " AWS::Lambda::Version "
2017-05-24 12:54:00 +00:00
@classmethod
2022-10-22 11:40:20 +00:00
def create_from_cloudformation_json ( # type: ignore[misc]
cls ,
resource_name : str ,
cloudformation_json : Dict [ str , Any ] ,
account_id : str ,
region_name : str ,
* * kwargs : Any ,
) - > " LambdaVersion " :
2017-05-24 12:54:00 +00:00
properties = cloudformation_json [ " Properties " ]
2019-08-21 01:54:57 +00:00
function_name = properties [ " FunctionName " ]
2022-08-13 09:49:43 +00:00
func = lambda_backends [ account_id ] [ region_name ] . publish_function ( function_name )
2023-12-01 11:51:28 +00:00
spec = { " Version " : func . version } # type: ignore[union-attr]
2017-05-24 12:54:00 +00:00
return LambdaVersion ( spec )
2017-11-26 21:28:28 +00:00
class LambdaStorage ( object ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self , region_name : str , account_id : str ) :
2022-03-17 12:32:31 +00:00
# Format 'func_name' {'versions': []}
2022-10-22 11:40:20 +00:00
self . _functions : Dict [ str , Any ] = { }
self . _arns : weakref . WeakValueDictionary [ str , LambdaFunction ] = (
weakref . WeakValueDictionary ( )
2024-03-21 20:17:29 +00:00
)
2022-03-15 15:07:01 +00:00
self . region_name = region_name
2022-08-13 09:49:43 +00:00
self . account_id = account_id
2017-11-26 21:28:28 +00:00
2023-07-01 10:32:33 +00:00
# function-arn -> alias -> LambdaAlias
self . _aliases : Dict [ str , Dict [ str , LambdaAlias ] ] = defaultdict ( lambda : { } )
2022-10-22 11:40:20 +00:00
def _get_latest ( self , name : str ) - > LambdaFunction :
2017-11-26 21:28:28 +00:00
return self . _functions [ name ] [ " latest " ]
2022-10-22 11:40:20 +00:00
def _get_version ( self , name : str , version : str ) - > Optional [ LambdaFunction ] :
2022-10-18 09:35:17 +00:00
for config in self . _functions [ name ] [ " versions " ] :
2023-07-01 10:32:33 +00:00
if str ( config . version ) == version :
2022-10-18 09:35:17 +00:00
return config
2023-07-01 10:32:33 +00:00
2022-10-18 09:35:17 +00:00
return None
2017-11-26 21:28:28 +00:00
2023-07-01 10:32:33 +00:00
def _get_function_aliases ( self , function_name : str ) - > Dict [ str , LambdaAlias ] :
2023-11-10 19:13:13 +00:00
fn = self . get_function_by_name_or_arn_with_qualifier ( function_name )
# Split ARN to retrieve an ARN without a qualifier present
[ arn , _ , _ ] = self . split_function_arn ( fn . function_arn )
return self . _aliases [ arn ]
2023-07-01 10:32:33 +00:00
def delete_alias ( self , name : str , function_name : str ) - > None :
aliases = self . _get_function_aliases ( function_name )
aliases . pop ( name , None )
2022-03-17 12:32:31 +00:00
2022-10-22 11:40:20 +00:00
def get_alias ( self , name : str , function_name : str ) - > LambdaAlias :
2023-07-01 10:32:33 +00:00
aliases = self . _get_function_aliases ( function_name )
if name in aliases :
return aliases [ name ]
arn = f " arn:aws:lambda: { self . region_name } : { self . account_id } :function: { function_name } : { name } "
raise UnknownAliasException ( arn )
2022-03-17 12:32:31 +00:00
def put_alias (
2022-10-22 11:40:20 +00:00
self ,
name : str ,
function_name : str ,
function_version : str ,
description : str ,
routing_config : str ,
) - > LambdaAlias :
2023-11-10 19:13:13 +00:00
fn = self . get_function_by_name_or_arn_with_qualifier (
function_name , function_version
)
2023-07-01 10:32:33 +00:00
aliases = self . _get_function_aliases ( function_name )
if name in aliases :
arn = f " arn:aws:lambda: { self . region_name } : { self . account_id } :function: { function_name } : { name } "
raise ConflictException ( f " Alias already exists: { arn } " )
alias = LambdaAlias (
account_id = self . account_id ,
region = self . region_name ,
name = name ,
function_name = fn . function_name ,
function_version = function_version ,
description = description ,
routing_config = routing_config ,
)
aliases [ name ] = alias
return alias
2022-03-17 12:32:31 +00:00
def update_alias (
2022-10-22 11:40:20 +00:00
self ,
name : str ,
function_name : str ,
function_version : str ,
description : str ,
routing_config : str ,
) - > LambdaAlias :
2023-07-01 10:32:33 +00:00
alias = self . get_alias ( name , function_name )
# errors if new function version doesn't exist
2023-11-10 19:13:13 +00:00
self . get_function_by_name_or_arn_with_qualifier ( function_name , function_version )
2023-07-01 10:32:33 +00:00
alias . update ( description , function_version , routing_config )
return alias
2017-11-26 21:28:28 +00:00
2023-11-10 19:13:13 +00:00
def get_function_by_name_forbid_qualifier ( self , name : str ) - > LambdaFunction :
"""
Get function by name forbidding a qualifier
: raises : UnknownFunctionException if function not found
: raises : InvalidParameterValue if qualifier is provided
"""
if name . count ( " : " ) == 1 :
raise InvalidParameterValueException ( " Cannot provide qualifier " )
2017-11-26 21:28:28 +00:00
if name not in self . _functions :
2023-11-10 19:13:13 +00:00
raise self . construct_unknown_function_exception ( name )
return self . _get_latest ( name )
2017-11-26 21:28:28 +00:00
2023-11-10 19:13:13 +00:00
def get_function_by_name_with_qualifier (
self , name : str , qualifier : Optional [ str ] = None
) - > LambdaFunction :
"""
Get function by name with an optional qualifier
: raises : UnknownFunctionException if function not found
"""
# Function name may contain an alias
# <fn_name>:<alias_name>
if " : " in name :
# Prefer qualifier in name over qualifier arg
[ name , qualifier ] = name . split ( " : " )
# Find without qualifier
2017-11-26 21:28:28 +00:00
if qualifier is None :
2023-11-10 19:13:13 +00:00
return self . get_function_by_name_forbid_qualifier ( name )
if name not in self . _functions :
raise self . construct_unknown_function_exception ( name , qualifier )
2017-11-26 21:28:28 +00:00
2023-11-10 19:13:13 +00:00
# Find by latest
2022-10-18 09:35:17 +00:00
if qualifier . lower ( ) == " $latest " :
2017-11-26 21:28:28 +00:00
return self . _functions [ name ] [ " latest " ]
2023-11-10 19:13:13 +00:00
# Find by version
2023-07-01 10:32:33 +00:00
found_version = self . _get_version ( name , qualifier )
if found_version :
return found_version
2023-11-10 19:13:13 +00:00
# Find by alias
2023-07-01 10:32:33 +00:00
aliases = self . _get_function_aliases ( name )
if qualifier in aliases :
2023-11-10 19:13:13 +00:00
alias_version = aliases [ qualifier ] . function_version
2023-07-01 10:32:33 +00:00
2023-11-10 19:13:13 +00:00
# Find by alias pointing to latest
if alias_version . lower ( ) == " $latest " :
return self . _functions [ name ] [ " latest " ]
# Find by alias pointing to version
found_alias = self . _get_version ( name , alias_version )
if found_alias :
return found_alias
raise self . construct_unknown_function_exception ( name , qualifier )
2022-10-18 09:35:17 +00:00
2022-10-22 11:40:20 +00:00
def list_versions_by_function ( self , name : str ) - > Iterable [ LambdaFunction ] :
2019-02-16 15:27:23 +00:00
if name not in self . _functions :
2022-10-22 11:40:20 +00:00
return [ ]
2019-05-21 16:49:56 +00:00
latest = copy . copy ( self . _functions [ name ] [ " latest " ] )
latest . function_arn + = " :$LATEST "
return [ latest ] + self . _functions [ name ] [ " versions " ]
2019-02-16 15:27:23 +00:00
2023-07-01 10:32:33 +00:00
def list_aliases ( self , function_name : str ) - > Iterable [ LambdaAlias ] :
aliases = self . _get_function_aliases ( function_name )
return sorted ( aliases . values ( ) , key = lambda alias : alias . name )
2022-10-22 11:40:20 +00:00
def get_arn ( self , arn : str ) - > Optional [ LambdaFunction ] :
2023-11-10 19:13:13 +00:00
[ arn_without_qualifier , _ , _ ] = self . split_function_arn ( arn )
return self . _arns . get ( arn_without_qualifier , None )
def split_function_arn ( self , arn : str ) - > Tuple [ str , str , Optional [ str ] ] :
"""
Handy utility to parse an ARN into :
- ARN without qualifier
- Function name
- Optional qualifier
"""
qualifier = None
2022-03-17 12:32:31 +00:00
# Function ARN may contain an alias
# arn:aws:lambda:region:account_id:function:<fn_name>:<alias_name>
if " : " in arn . split ( " :function: " ) [ - 1 ] :
2023-11-10 19:13:13 +00:00
qualifier = arn . split ( " : " ) [ - 1 ]
2022-03-17 12:32:31 +00:00
# arn = arn:aws:lambda:region:account_id:function:<fn_name>
arn = " : " . join ( arn . split ( " : " ) [ 0 : - 1 ] )
2023-11-10 19:13:13 +00:00
name = arn . split ( " : " ) [ - 1 ]
return arn , name , qualifier
def get_function_by_name_or_arn_forbid_qualifier (
self , name_or_arn : str
) - > LambdaFunction :
"""
Get function by name or arn forbidding a qualifier
: raises : UnknownFunctionException if function not found
: raises : InvalidParameterValue if qualifier is provided
"""
if name_or_arn . startswith ( " arn:aws " ) :
[ _ , name , qualifier ] = self . split_function_arn ( name_or_arn )
2017-11-26 21:28:28 +00:00
2023-11-10 19:13:13 +00:00
if qualifier is not None :
raise InvalidParameterValueException ( " Cannot provide qualifier " )
return self . get_function_by_name_forbid_qualifier ( name )
else :
# name_or_arn is not an arn
return self . get_function_by_name_forbid_qualifier ( name_or_arn )
def get_function_by_name_or_arn_with_qualifier (
2022-10-22 11:40:20 +00:00
self , name_or_arn : str , qualifier : Optional [ str ] = None
2022-08-24 21:19:17 +00:00
) - > LambdaFunction :
2023-11-10 19:13:13 +00:00
"""
Get function by name or arn with an optional qualifier
: raises : UnknownFunctionException if function not found
"""
if name_or_arn . startswith ( " arn:aws " ) :
[ _ , name , qualifier_in_arn ] = self . split_function_arn ( name_or_arn )
return self . get_function_by_name_with_qualifier (
name , qualifier_in_arn or qualifier
)
else :
return self . get_function_by_name_with_qualifier ( name_or_arn , qualifier )
def construct_unknown_function_exception (
self , name_or_arn : str , qualifier : Optional [ str ] = None
) - > UnknownFunctionException :
if name_or_arn . startswith ( " arn:aws " ) :
arn = name_or_arn
else :
# name_or_arn is a function name with optional qualifier <func_name>[:<qualifier>]
arn = make_function_arn ( self . region_name , self . account_id , name_or_arn )
# Append explicit qualifier to arn only if the name doesn't already have it
if qualifier and " : " not in name_or_arn :
2022-03-19 13:00:39 +00:00
arn = f " { arn } : { qualifier } "
2023-11-10 19:13:13 +00:00
return UnknownFunctionException ( arn )
2019-08-21 01:54:57 +00:00
2022-10-22 11:40:20 +00:00
def put_function ( self , fn : LambdaFunction ) - > None :
2019-11-17 10:59:20 +00:00
valid_role = re . match ( InvalidRoleFormat . pattern , fn . role )
2019-11-07 17:11:13 +00:00
if valid_role :
account = valid_role . group ( 2 )
2022-08-13 09:49:43 +00:00
if account != self . account_id :
2019-11-17 10:59:20 +00:00
raise CrossAccountNotAllowed ( )
2019-11-07 17:11:13 +00:00
try :
2022-08-13 09:49:43 +00:00
iam_backend = iam_backends [ self . account_id ] [ " global " ]
iam_backend . get_role_by_arn ( fn . role )
2019-11-07 17:11:13 +00:00
except IAMNotFoundException :
2019-11-17 10:59:20 +00:00
raise InvalidParameterValueException (
" The role defined for the function cannot be assumed by Lambda. "
2019-11-07 17:11:13 +00:00
)
else :
2019-11-17 10:59:20 +00:00
raise InvalidRoleFormat ( fn . role )
2017-11-26 21:28:28 +00:00
if fn . function_name in self . _functions :
self . _functions [ fn . function_name ] [ " latest " ] = fn
else :
2022-03-17 12:32:31 +00:00
self . _functions [ fn . function_name ] = { " latest " : fn , " versions " : [ ] }
2017-11-26 21:28:28 +00:00
self . _arns [ fn . function_arn ] = fn
2022-10-22 11:40:20 +00:00
def publish_function (
self , name_or_arn : str , description : str = " "
) - > Optional [ LambdaFunction ] :
2023-11-10 19:13:13 +00:00
function = self . get_function_by_name_or_arn_forbid_qualifier ( name_or_arn )
2021-08-28 06:23:44 +00:00
name = function . function_name
2017-11-26 21:28:28 +00:00
if name not in self . _functions :
return None
if not self . _functions [ name ] [ " latest " ] :
return None
2022-12-05 10:31:20 +00:00
all_versions = self . _functions [ name ] [ " versions " ]
if all_versions :
latest_published = all_versions [ - 1 ]
if latest_published . code_sha_256 == function . code_sha_256 :
# Nothing has changed, don't publish
return latest_published
new_version = len ( all_versions ) + 1
2017-11-26 21:28:28 +00:00
fn = copy . copy ( self . _functions [ name ] [ " latest " ] )
fn . set_version ( new_version )
2021-08-28 14:26:44 +00:00
if description :
fn . description = description
2017-11-26 21:28:28 +00:00
self . _functions [ name ] [ " versions " ] . append ( fn )
2019-05-21 16:49:56 +00:00
self . _arns [ fn . function_arn ] = fn
2017-11-26 21:28:28 +00:00
return fn
2022-10-22 11:40:20 +00:00
def del_function ( self , name_or_arn : str , qualifier : Optional [ str ] = None ) - > None :
2023-11-10 19:13:13 +00:00
# Qualifier may be explicitly passed or part of function name or ARN, extract it here
if name_or_arn . startswith ( " arn:aws " ) :
# Extract from ARN
if " : " in name_or_arn . split ( " :function: " ) [ - 1 ] :
qualifier = name_or_arn . split ( " : " ) [ - 1 ]
else :
# Extract from function name
if " : " in name_or_arn :
qualifier = name_or_arn . split ( " : " ) [ 1 ]
function = self . get_function_by_name_or_arn_with_qualifier (
name_or_arn , qualifier
)
2022-03-19 13:00:39 +00:00
name = function . function_name
if not qualifier :
# Something is still reffing this so delete all arns
latest = self . _functions [ name ] [ " latest " ] . function_arn
del self . _arns [ latest ]
2017-11-26 21:28:28 +00:00
2022-03-19 13:00:39 +00:00
for fn in self . _functions [ name ] [ " versions " ] :
del self . _arns [ fn . function_arn ]
2017-11-26 21:28:28 +00:00
2022-03-19 13:00:39 +00:00
del self . _functions [ name ]
2017-11-26 21:28:28 +00:00
2023-11-10 19:13:13 +00:00
else :
if qualifier == " $LATEST " :
self . _functions [ name ] [ " latest " ] = None
else :
self . _functions [ name ] [ " versions " ] . remove ( function )
2017-11-26 21:28:28 +00:00
2022-03-19 13:00:39 +00:00
# If theres no functions left
if (
not self . _functions [ name ] [ " versions " ]
and not self . _functions [ name ] [ " latest " ]
) :
del self . _functions [ name ]
2023-07-01 10:32:33 +00:00
self . _aliases [ function . function_arn ] = { }
2022-10-22 11:40:20 +00:00
def all ( self ) - > Iterable [ LambdaFunction ] :
2017-11-26 21:28:28 +00:00
result = [ ]
2023-08-31 06:47:49 +00:00
for function_group in list ( self . _functions . values ( ) ) :
2021-08-28 14:26:44 +00:00
latest = copy . deepcopy ( function_group [ " latest " ] )
2022-11-12 13:32:07 +00:00
latest . function_arn = f " { latest . function_arn } :$LATEST "
2021-08-28 14:26:44 +00:00
result . append ( latest )
2017-11-26 21:28:28 +00:00
result . extend ( function_group [ " versions " ] )
return result
2022-10-22 11:40:20 +00:00
def latest ( self ) - > Iterable [ LambdaFunction ] :
2021-08-28 14:26:44 +00:00
"""
Return the list of functions with version @LATEST
: return :
"""
result = [ ]
for function_group in self . _functions . values ( ) :
if function_group [ " latest " ] is not None :
result . append ( function_group [ " latest " ] )
return result
2017-11-26 21:28:28 +00:00
2021-01-17 15:28:49 +00:00
class LayerStorage ( object ) :
2022-10-22 11:40:20 +00:00
def __init__ ( self ) - > None :
self . _layers : Dict [ str , Layer ] = { }
self . _arns : weakref . WeakValueDictionary [ str , LambdaFunction ] = (
weakref . WeakValueDictionary ( )
2024-03-21 20:17:29 +00:00
)
2021-01-17 15:28:49 +00:00
2023-06-15 11:05:06 +00:00
def _find_layer_by_name_or_arn ( self , name_or_arn : str ) - > Layer :
if name_or_arn in self . _layers :
return self . _layers [ name_or_arn ]
for layer in self . _layers . values ( ) :
if layer . layer_arn == name_or_arn :
return layer
raise UnknownLayerException ( )
2022-10-22 11:40:20 +00:00
def put_layer_version ( self , layer_version : LayerVersion ) - > None :
2021-01-17 15:28:49 +00:00
"""
: param layer_version : LayerVersion
"""
if layer_version . name not in self . _layers :
2022-08-13 09:49:43 +00:00
self . _layers [ layer_version . name ] = Layer ( layer_version )
2021-01-17 15:28:49 +00:00
self . _layers [ layer_version . name ] . attach_version ( layer_version )
2022-10-22 11:40:20 +00:00
def list_layers ( self ) - > Iterable [ Dict [ str , Any ] ] :
2022-08-12 16:32:41 +00:00
return [
layer . to_dict ( ) for layer in self . _layers . values ( ) if layer . layer_versions
]
2021-01-17 15:28:49 +00:00
2022-10-22 11:40:20 +00:00
def delete_layer_version ( self , layer_name : str , layer_version : str ) - > None :
2023-06-15 11:05:06 +00:00
layer = self . _find_layer_by_name_or_arn ( layer_name )
layer . delete_version ( layer_version )
2022-03-17 12:32:31 +00:00
2022-10-22 11:40:20 +00:00
def get_layer_version ( self , layer_name : str , layer_version : str ) - > LayerVersion :
2023-06-15 11:05:06 +00:00
layer = self . _find_layer_by_name_or_arn ( layer_name )
for lv in layer . layer_versions . values ( ) :
2022-03-17 12:32:31 +00:00
if lv . version == int ( layer_version ) :
return lv
raise UnknownLayerException ( )
2022-10-22 11:40:20 +00:00
def get_layer_versions ( self , layer_name : str ) - > List [ LayerVersion ] :
2021-01-17 15:28:49 +00:00
if layer_name in self . _layers :
return list ( iter ( self . _layers [ layer_name ] . layer_versions . values ( ) ) )
return [ ]
2022-10-22 11:40:20 +00:00
def get_layer_version_by_arn (
self , layer_version_arn : str
) - > Optional [ LayerVersion ] :
2021-01-17 15:28:49 +00:00
split_arn = split_layer_arn ( layer_version_arn )
if split_arn . layer_name in self . _layers :
return self . _layers [ split_arn . layer_name ] . layer_versions . get (
split_arn . version , None
)
return None
2016-02-12 19:39:20 +00:00
class LambdaBackend ( BaseBackend ) :
2022-01-27 12:04:03 +00:00
"""
Implementation of the AWS Lambda endpoint .
Invoking functions is supported - they will run inside a Docker container , emulating the real AWS behaviour as closely as possible .
2024-02-20 23:06:46 +00:00
. . warning : : When invoking a function using the decorators , the created Docker container cannot reach Moto ( or it ' s in-memory state). Any AWS SDK-requests within your Lambda will try to connect to AWS instead.
It is possible to connect from AWS Lambdas to other services , as long as you are running MotoProxy or the MotoServer in a Docker container .
2023-09-27 18:34:30 +00:00
When running the MotoProxy , calls to other AWS services are automatically proxied .
When running MotoServer , the Lambda has access to environment variables ` MOTO_HOST ` and ` MOTO_PORT ` , which can be used to build the url that MotoServer runs on :
2022-01-27 12:04:03 +00:00
. . sourcecode : : python
def lambda_handler ( event , context ) :
host = os . environ . get ( " MOTO_HOST " )
port = os . environ . get ( " MOTO_PORT " )
url = host + " : " + port
ec2 = boto3 . client ( ' ec2 ' , region_name = ' us-west-2 ' , endpoint_url = url )
# Or even simpler:
full_url = os . environ . get ( " MOTO_HTTP_ENDPOINT " )
ec2 = boto3 . client ( " ec2 " , region_name = " eu-west-1 " , endpoint_url = full_url )
ec2 . do_whatever_inside_the_existing_moto_server ( )
Moto will run on port 5000 by default . This can be overwritten by setting an environment variable when starting Moto :
. . sourcecode : : bash
2022-02-26 11:21:11 +00:00
# This env var will be propagated to the Docker container running the Lambda functions
2022-01-27 12:04:03 +00:00
MOTO_PORT = 5000 moto_server
The Docker container uses the default network mode , ` bridge ` .
The following environment variables are available for fine - grained control over the Docker connection options :
. . sourcecode : : bash
# Provide the name of a custom network to connect to
MOTO_DOCKER_NETWORK_NAME = mycustomnetwork moto_server
# Override the network mode
# For example, network_mode=host would use the network of the host machine
# Note that this option will be ignored if MOTO_DOCKER_NETWORK_NAME is also set
MOTO_DOCKER_NETWORK_MODE = host moto_server
2023-11-10 22:44:59 +00:00
_ - _ - _ - _
2023-02-02 12:56:50 +00:00
The Docker images used by Moto are taken from the following repositories :
- ` mlupin / docker - lambda ` ( for recent versions )
- ` lambci / lambda ` ( for older / outdated versions )
Use the following environment variable to configure Moto to look for images in an additional repository :
2022-02-26 11:21:11 +00:00
. . sourcecode : : bash
2023-02-02 12:56:50 +00:00
MOTO_DOCKER_LAMBDA_IMAGE = mlupin / docker - lambda
2022-01-27 12:04:03 +00:00
2023-11-10 22:44:59 +00:00
_ - _ - _ - _
2023-03-18 10:35:48 +00:00
Use the following environment variable if you want to configure the data directory used by the Docker containers :
. . sourcecode : : bash
MOTO_LAMBDA_DATA_DIR = / tmp / data
2023-11-10 22:44:59 +00:00
_ - _ - _ - _
If you want to mock the Lambda - containers invocation without starting a Docker - container , use the simple decorator :
. . sourcecode : : python
2024-02-20 23:06:46 +00:00
@mock_aws ( config = { " lambda " : { " use_docker " : False } } )
2023-11-10 22:44:59 +00:00
2022-01-27 12:04:03 +00:00
"""
2022-10-22 11:40:20 +00:00
def __init__ ( self , region_name : str , account_id : str ) :
2022-06-04 11:30:16 +00:00
super ( ) . __init__ ( region_name , account_id )
2022-08-13 09:49:43 +00:00
self . _lambdas = LambdaStorage ( region_name = region_name , account_id = account_id )
2022-10-22 11:40:20 +00:00
self . _event_source_mappings : Dict [ str , EventSourceMapping ] = { }
2021-01-17 15:28:49 +00:00
self . _layers = LayerStorage ( )
2017-02-17 03:51:04 +00:00
2022-03-17 12:32:31 +00:00
def create_alias (
2022-10-22 11:40:20 +00:00
self ,
name : str ,
function_name : str ,
function_version : str ,
description : str ,
routing_config : str ,
) - > LambdaAlias :
2022-03-17 12:32:31 +00:00
return self . _lambdas . put_alias (
name , function_name , function_version , description , routing_config
)
2022-10-22 11:40:20 +00:00
def delete_alias ( self , name : str , function_name : str ) - > None :
2022-03-17 12:32:31 +00:00
return self . _lambdas . delete_alias ( name , function_name )
2022-10-22 11:40:20 +00:00
def get_alias ( self , name : str , function_name : str ) - > LambdaAlias :
2022-03-17 12:32:31 +00:00
return self . _lambdas . get_alias ( name , function_name )
def update_alias (
2022-10-22 11:40:20 +00:00
self ,
name : str ,
function_name : str ,
function_version : str ,
description : str ,
routing_config : str ,
) - > LambdaAlias :
2022-03-17 12:32:31 +00:00
"""
The RevisionId parameter is not yet implemented
"""
return self . _lambdas . update_alias (
name , function_name , function_version , description , routing_config
)
2022-10-22 11:40:20 +00:00
def create_function ( self , spec : Dict [ str , Any ] ) - > LambdaFunction :
2022-11-30 21:09:37 +00:00
"""
The Code . ImageUri is not validated by default . Set environment variable MOTO_LAMBDA_STUB_ECR = false if you want to validate the image exists in our mocked ECR .
"""
2017-11-26 21:28:28 +00:00
function_name = spec . get ( " FunctionName " , None )
if function_name is None :
raise RESTError ( " InvalidParameterValueException " , " Missing FunctionName " )
2016-02-12 19:39:20 +00:00
2022-08-13 09:49:43 +00:00
fn = LambdaFunction (
account_id = self . account_id ,
spec = spec ,
region = self . region_name ,
version = " $LATEST " ,
)
2017-11-26 21:28:28 +00:00
self . _lambdas . put_function ( fn )
2017-09-13 19:00:39 +00:00
2019-05-21 16:49:56 +00:00
if spec . get ( " Publish " ) :
ver = self . publish_function ( function_name )
2021-08-28 14:26:44 +00:00
fn = copy . deepcopy (
fn
) # We don't want to change the actual version - just the return value
2022-10-22 11:40:20 +00:00
fn . version = ver . version # type: ignore[union-attr]
2016-02-12 19:39:20 +00:00
return fn
2022-10-22 11:40:20 +00:00
def create_function_url_config (
self , name_or_arn : str , config : Dict [ str , Any ]
) - > FunctionUrlConfig :
2022-08-24 21:19:17 +00:00
"""
The Qualifier - parameter is not yet implemented .
Function URLs are not yet mocked , so invoking them will fail
"""
2023-11-10 19:13:13 +00:00
function = self . _lambdas . get_function_by_name_or_arn_forbid_qualifier (
name_or_arn
)
2022-08-24 21:19:17 +00:00
return function . create_url_config ( config )
2022-10-22 11:40:20 +00:00
def delete_function_url_config ( self , name_or_arn : str ) - > None :
2022-08-24 21:19:17 +00:00
"""
The Qualifier - parameter is not yet implemented
"""
2023-11-10 19:13:13 +00:00
function = self . _lambdas . get_function_by_name_or_arn_forbid_qualifier (
name_or_arn
)
2022-08-24 21:19:17 +00:00
function . delete_url_config ( )
2022-10-22 11:40:20 +00:00
def get_function_url_config ( self , name_or_arn : str ) - > FunctionUrlConfig :
2022-08-24 21:19:17 +00:00
"""
The Qualifier - parameter is not yet implemented
"""
2023-11-10 19:13:13 +00:00
function = self . _lambdas . get_function_by_name_or_arn_forbid_qualifier (
name_or_arn
)
2022-08-24 21:19:17 +00:00
if not function :
raise UnknownFunctionException ( arn = name_or_arn )
return function . get_url_config ( )
2022-10-22 11:40:20 +00:00
def update_function_url_config (
self , name_or_arn : str , config : Dict [ str , Any ]
) - > FunctionUrlConfig :
2022-08-24 21:19:17 +00:00
"""
The Qualifier - parameter is not yet implemented
"""
2023-11-10 19:13:13 +00:00
function = self . _lambdas . get_function_by_name_or_arn_forbid_qualifier (
name_or_arn
)
2022-08-24 21:19:17 +00:00
return function . update_url_config ( config )
2022-10-22 11:40:20 +00:00
def create_event_source_mapping ( self , spec : Dict [ str , Any ] ) - > EventSourceMapping :
2019-08-21 01:54:57 +00:00
required = [ " EventSourceArn " , " FunctionName " ]
for param in required :
if not spec . get ( param ) :
2022-11-12 13:32:07 +00:00
raise RESTError ( " InvalidParameterValueException " , f " Missing { param } " )
2019-08-21 01:54:57 +00:00
# Validate function name
2023-11-10 19:13:13 +00:00
func = self . _lambdas . get_function_by_name_or_arn_with_qualifier (
spec . get ( " FunctionName " , " " )
)
2019-08-21 01:54:57 +00:00
# Validate queue
2022-08-13 09:49:43 +00:00
sqs_backend = sqs_backends [ self . account_id ] [ self . region_name ]
for queue in sqs_backend . queues . values ( ) :
2019-08-21 01:54:57 +00:00
if queue . queue_arn == spec [ " EventSourceArn " ] :
if queue . lambda_event_source_mappings . get ( " func.function_arn " ) :
# TODO: Correct exception?
raise RESTError (
" ResourceConflictException " , " The resource already exists. "
)
2023-03-01 15:03:20 +00:00
spec . update ( { " FunctionArn " : func . function_arn } )
esm = EventSourceMapping ( spec )
self . _event_source_mappings [ esm . uuid ] = esm
2019-08-21 01:54:57 +00:00
2023-03-01 15:03:20 +00:00
# Set backend function on queue
queue . lambda_event_source_mappings [ esm . function_arn ] = esm
return esm
2019-08-21 01:54:57 +00:00
2022-08-13 09:49:43 +00:00
ddbstream_backend = dynamodbstreams_backends [ self . account_id ] [ self . region_name ]
ddb_backend = dynamodb_backends [ self . account_id ] [ self . region_name ]
for stream in json . loads ( ddbstream_backend . list_streams ( ) ) [ " Streams " ] :
2019-10-08 13:11:21 +00:00
if stream [ " StreamArn " ] == spec [ " EventSourceArn " ] :
spec . update ( { " FunctionArn " : func . function_arn } )
esm = EventSourceMapping ( spec )
self . _event_source_mappings [ esm . uuid ] = esm
table_name = stream [ " TableName " ]
2022-08-13 09:49:43 +00:00
table = ddb_backend . get_table ( table_name )
2019-10-08 13:11:21 +00:00
table . lambda_event_source_mappings [ esm . function_arn ] = esm
return esm
2024-01-30 20:51:15 +00:00
kinesis_backend : KinesisBackend = kinesis_backends [ self . account_id ] [
self . region_name
]
for stream in kinesis_backend . streams . values ( ) :
if stream . arn == spec [ " EventSourceArn " ] :
spec . update ( { " FunctionArn " : func . function_arn } )
esm = EventSourceMapping ( spec )
self . _event_source_mappings [ esm . uuid ] = esm
stream . lambda_event_source_mappings [ esm . event_source_arn ] = esm
return esm
2019-08-21 01:54:57 +00:00
raise RESTError ( " ResourceNotFoundException " , " Invalid EventSourceArn " )
2022-10-22 11:40:20 +00:00
def publish_layer_version ( self , spec : Dict [ str , Any ] ) - > LayerVersion :
2021-01-17 15:28:49 +00:00
required = [ " LayerName " , " Content " ]
for param in required :
if not spec . get ( param ) :
2022-11-12 13:32:07 +00:00
raise InvalidParameterValueException ( f " Missing { param } " )
2022-08-13 09:49:43 +00:00
layer_version = LayerVersion (
spec , account_id = self . account_id , region = self . region_name
)
2021-01-17 15:28:49 +00:00
self . _layers . put_layer_version ( layer_version )
return layer_version
2022-10-22 11:40:20 +00:00
def list_layers ( self ) - > Iterable [ Dict [ str , Any ] ] :
2021-01-17 15:28:49 +00:00
return self . _layers . list_layers ( )
2022-10-22 11:40:20 +00:00
def delete_layer_version ( self , layer_name : str , layer_version : str ) - > None :
2022-03-17 12:32:31 +00:00
return self . _layers . delete_layer_version ( layer_name , layer_version )
2022-10-22 11:40:20 +00:00
def get_layer_version ( self , layer_name : str , layer_version : str ) - > LayerVersion :
2022-03-17 12:32:31 +00:00
return self . _layers . get_layer_version ( layer_name , layer_version )
2022-10-22 11:40:20 +00:00
def get_layer_versions ( self , layer_name : str ) - > Iterable [ LayerVersion ] :
2021-01-17 15:28:49 +00:00
return self . _layers . get_layer_versions ( layer_name )
2022-10-22 11:40:20 +00:00
def layers_versions_by_arn ( self , layer_version_arn : str ) - > Optional [ LayerVersion ] :
2021-01-17 15:28:49 +00:00
return self . _layers . get_layer_version_by_arn ( layer_version_arn )
2022-10-22 11:40:20 +00:00
def publish_function (
self , function_name : str , description : str = " "
) - > Optional [ LambdaFunction ] :
2021-08-28 14:26:44 +00:00
return self . _lambdas . publish_function ( function_name , description )
2017-11-26 21:28:28 +00:00
2022-10-22 11:40:20 +00:00
def get_function (
self , function_name_or_arn : str , qualifier : Optional [ str ] = None
) - > LambdaFunction :
2023-11-10 19:13:13 +00:00
return self . _lambdas . get_function_by_name_or_arn_with_qualifier (
2019-11-04 15:22:03 +00:00
function_name_or_arn , qualifier
)
2016-02-12 19:39:20 +00:00
2022-10-22 11:40:20 +00:00
def list_versions_by_function ( self , function_name : str ) - > Iterable [ LambdaFunction ] :
2019-02-16 15:27:23 +00:00
return self . _lambdas . list_versions_by_function ( function_name )
2023-07-01 10:32:33 +00:00
def list_aliases ( self , function_name : str ) - > Iterable [ LambdaAlias ] :
return self . _lambdas . list_aliases ( function_name )
2022-10-22 11:40:20 +00:00
def get_event_source_mapping ( self , uuid : str ) - > Optional [ EventSourceMapping ] :
2019-08-21 01:54:57 +00:00
return self . _event_source_mappings . get ( uuid )
2022-10-22 11:40:20 +00:00
def delete_event_source_mapping ( self , uuid : str ) - > Optional [ EventSourceMapping ] :
return self . _event_source_mappings . pop ( uuid , None )
2019-08-21 01:54:57 +00:00
2022-10-22 11:40:20 +00:00
def update_event_source_mapping (
self , uuid : str , spec : Dict [ str , Any ]
) - > Optional [ EventSourceMapping ] :
2019-08-21 01:54:57 +00:00
esm = self . get_event_source_mapping ( uuid )
2020-06-14 15:03:00 +00:00
if not esm :
2022-10-22 11:40:20 +00:00
return None
2020-06-14 15:03:00 +00:00
2021-12-01 23:06:58 +00:00
for key in spec . keys ( ) :
2020-06-14 15:03:00 +00:00
if key == " FunctionName " :
2023-11-10 19:13:13 +00:00
func = self . _lambdas . get_function_by_name_or_arn_with_qualifier (
spec [ key ]
)
2019-08-21 01:54:57 +00:00
esm . function_arn = func . function_arn
2020-06-14 15:03:00 +00:00
elif key == " BatchSize " :
esm . batch_size = spec [ key ]
elif key == " Enabled " :
esm . enabled = spec [ key ]
2023-09-11 22:23:44 +00:00
esm . last_modified = time . mktime ( utcnow ( ) . timetuple ( ) )
2020-06-14 15:03:00 +00:00
return esm
2019-08-21 01:54:57 +00:00
2022-10-22 11:40:20 +00:00
def list_event_source_mappings (
self , event_source_arn : str , function_name : str
) - > Iterable [ EventSourceMapping ] :
2019-08-21 01:54:57 +00:00
esms = list ( self . _event_source_mappings . values ( ) )
if event_source_arn :
esms = list ( filter ( lambda x : x . event_source_arn == event_source_arn , esms ) )
if function_name :
esms = list ( filter ( lambda x : x . function_name == function_name , esms ) )
return esms
2022-10-22 11:40:20 +00:00
def get_function_by_arn ( self , function_arn : str ) - > Optional [ LambdaFunction ] :
2017-11-26 21:28:28 +00:00
return self . _lambdas . get_arn ( function_arn )
2017-09-13 19:00:39 +00:00
2022-10-22 11:40:20 +00:00
def delete_function (
self , function_name : str , qualifier : Optional [ str ] = None
) - > None :
2022-03-19 13:00:39 +00:00
self . _lambdas . del_function ( function_name , qualifier )
2016-02-12 19:39:20 +00:00
2022-10-22 11:40:20 +00:00
def list_functions (
self , func_version : Optional [ str ] = None
) - > Iterable [ LambdaFunction ] :
2021-08-28 14:26:44 +00:00
if func_version == " ALL " :
return self . _lambdas . all ( )
return self . _lambdas . latest ( )
2016-02-12 19:39:20 +00:00
2022-10-22 11:40:20 +00:00
def send_sqs_batch ( self , function_arn : str , messages : Any , queue_arn : str ) - > bool :
2019-08-21 01:54:57 +00:00
success = True
for message in messages :
2024-02-11 14:47:34 +00:00
result = self . _send_sqs_message ( function_arn , message , queue_arn )
2019-08-21 01:54:57 +00:00
if not result :
success = False
return success
2022-10-22 11:40:20 +00:00
def _send_sqs_message (
2024-01-07 12:03:33 +00:00
self , function_arn : str , message : Any , queue_arn : str
2022-10-22 11:40:20 +00:00
) - > bool :
2019-08-21 01:54:57 +00:00
event = {
" Records " : [
{
" messageId " : message . id ,
" receiptHandle " : message . receipt_handle ,
" body " : message . body ,
" attributes " : {
" ApproximateReceiveCount " : " 1 " ,
" SentTimestamp " : " 1545082649183 " ,
" SenderId " : " AIDAIENQZJOLO23YVJ4VO " ,
" ApproximateFirstReceiveTimestamp " : " 1545082649185 " ,
} ,
" messageAttributes " : { } ,
" md5OfBody " : " 098f6bcd4621d373cade4e832627b4f6 " ,
" eventSource " : " aws:sqs " ,
" eventSourceARN " : queue_arn ,
" awsRegion " : self . region_name ,
}
]
}
2023-03-01 15:03:20 +00:00
if queue_arn . endswith ( " .fifo " ) :
# Messages from FIFO queue have additional attributes
event [ " Records " ] [ 0 ] [ " attributes " ] . update (
{
" MessageGroupId " : message . group_id ,
" MessageDeduplicationId " : message . deduplication_id ,
}
)
2019-08-21 01:54:57 +00:00
2022-10-22 11:40:20 +00:00
request_headers : Dict [ str , Any ] = { }
response_headers : Dict [ str , Any ] = { }
2024-01-07 12:03:33 +00:00
self . invoke (
function_name = function_arn ,
qualifier = None ,
body = json . dumps ( event ) ,
headers = request_headers ,
response_headers = response_headers ,
)
2019-08-21 01:54:57 +00:00
return " x-amz-function-error " not in response_headers
2024-01-30 20:51:15 +00:00
def send_kinesis_message (
self ,
function_name : str ,
kinesis_stream : str ,
kinesis_partition_key : str ,
kinesis_sequence_number : str ,
kinesis_data : str ,
kinesis_shard_id : str ,
) - > None :
func = self . _lambdas . get_function_by_name_or_arn_with_qualifier (
function_name , qualifier = None
)
event = {
" Records " : [
{
" kinesis " : {
" kinesisSchemaVersion " : " 1.0 " ,
" partitionKey " : kinesis_partition_key ,
" sequenceNumber " : kinesis_sequence_number ,
" data " : kinesis_data ,
" approximateArrivalTimestamp " : round ( time . time ( ) , 3 ) ,
} ,
" eventSource " : " aws:kinesis " ,
" eventVersion " : " 1.0 " ,
" eventID " : f " { kinesis_shard_id } : { kinesis_sequence_number } " ,
" eventName " : " aws:kinesis:record " ,
" invokeIdentityArn " : func . role ,
" awsRegion " : self . region_name ,
" eventSourceARN " : kinesis_stream ,
}
]
}
func . invoke ( json . dumps ( event ) , { } , { } )
2022-10-22 11:40:20 +00:00
def send_sns_message (
self ,
function_name : str ,
message : str ,
subject : Optional [ str ] = None ,
qualifier : Optional [ str ] = None ,
) - > None :
2017-09-27 23:04:58 +00:00
event = {
" Records " : [
{
" EventVersion " : " 1.0 " ,
" EventSubscriptionArn " : " arn:aws:sns:EXAMPLE " ,
" EventSource " : " aws:sns " ,
" Sns " : {
" SignatureVersion " : " 1 " ,
" Timestamp " : " 1970-01-01T00:00:00.000Z " ,
" Signature " : " EXAMPLE " ,
" SigningCertUrl " : " EXAMPLE " ,
" MessageId " : " 95df01b4-ee98-5cb9-9903-4c221d41eb5e " ,
" Message " : message ,
" MessageAttributes " : {
" Test " : { " Type " : " String " , " Value " : " TestString " } ,
" TestBinary " : { " Type " : " Binary " , " Value " : " TestBinary " } ,
} ,
" Type " : " Notification " ,
" UnsubscribeUrl " : " EXAMPLE " ,
" TopicArn " : " arn:aws:sns:EXAMPLE " ,
2017-12-10 21:59:04 +00:00
" Subject " : subject or " TestInvoke " ,
2017-09-27 23:04:58 +00:00
} ,
}
]
}
2023-11-10 19:13:13 +00:00
func = self . _lambdas . get_function_by_name_or_arn_with_qualifier (
function_name , qualifier
)
2023-07-20 15:46:54 +00:00
func . invoke ( json . dumps ( event ) , { } , { } )
2017-09-27 23:04:58 +00:00
2022-10-22 11:40:20 +00:00
def send_dynamodb_items (
self , function_arn : str , items : List [ Any ] , source : str
) - > Union [ str , bytes ] :
2019-10-07 10:11:22 +00:00
event = {
" Records " : [
{
" eventID " : item . to_json ( ) [ " eventID " ] ,
" eventName " : " INSERT " ,
" eventVersion " : item . to_json ( ) [ " eventVersion " ] ,
" eventSource " : item . to_json ( ) [ " eventSource " ] ,
2019-10-08 13:11:21 +00:00
" awsRegion " : self . region_name ,
2019-10-07 10:11:22 +00:00
" dynamodb " : item . to_json ( ) [ " dynamodb " ] ,
" eventSourceARN " : source ,
}
for item in items
2019-10-31 15:44:26 +00:00
]
2019-10-07 10:11:22 +00:00
}
func = self . _lambdas . get_arn ( function_arn )
2022-10-22 11:40:20 +00:00
return func . invoke ( json . dumps ( event ) , { } , { } ) # type: ignore[union-attr]
2019-10-07 10:11:22 +00:00
2020-05-12 12:34:10 +00:00
def send_log_event (
2022-10-22 11:40:20 +00:00
self ,
function_arn : str ,
filter_name : str ,
log_group_name : str ,
log_stream_name : str ,
log_events : Any ,
) - > None :
2020-05-12 12:34:10 +00:00
data = {
" messageType " : " DATA_MESSAGE " ,
2022-08-13 09:49:43 +00:00
" owner " : self . account_id ,
2020-05-12 12:34:10 +00:00
" logGroup " : log_group_name ,
" logStream " : log_stream_name ,
" subscriptionFilters " : [ filter_name ] ,
" logEvents " : log_events ,
}
output = io . BytesIO ( )
with GzipFile ( fileobj = output , mode = " w " ) as f :
f . write ( json . dumps ( data , separators = ( " , " , " : " ) ) . encode ( " utf-8 " ) )
payload_gz_encoded = base64 . b64encode ( output . getvalue ( ) ) . decode ( " utf-8 " )
event = { " awslogs " : { " data " : payload_gz_encoded } }
func = self . _lambdas . get_arn ( function_arn )
2022-10-22 11:40:20 +00:00
func . invoke ( json . dumps ( event ) , { } , { } ) # type: ignore[union-attr]
2020-05-12 12:34:10 +00:00
2022-10-22 11:40:20 +00:00
def list_tags ( self , resource : str ) - > Dict [ str , str ] :
2023-11-10 19:13:13 +00:00
return self . _lambdas . get_function_by_name_or_arn_with_qualifier ( resource ) . tags
2017-09-13 19:00:39 +00:00
2022-10-22 11:40:20 +00:00
def tag_resource ( self , resource : str , tags : Dict [ str , str ] ) - > None :
2023-11-10 19:13:13 +00:00
fn = self . _lambdas . get_function_by_name_or_arn_with_qualifier ( resource )
2017-11-26 21:28:28 +00:00
fn . tags . update ( tags )
2017-09-13 19:00:39 +00:00
2022-10-22 11:40:20 +00:00
def untag_resource ( self , resource : str , tagKeys : List [ str ] ) - > None :
2023-11-10 19:13:13 +00:00
fn = self . _lambdas . get_function_by_name_or_arn_with_qualifier ( resource )
2022-03-19 13:00:39 +00:00
for key in tagKeys :
fn . tags . pop ( key , None )
2022-10-22 11:40:20 +00:00
def add_permission (
self , function_name : str , qualifier : str , raw : str
) - > Dict [ str , Any ] :
2022-03-19 13:00:39 +00:00
fn = self . get_function ( function_name , qualifier )
2024-02-11 14:47:34 +00:00
return fn . policy . add_statement ( raw , qualifier )
2020-01-23 18:46:24 +00:00
2022-10-22 11:40:20 +00:00
def remove_permission (
self , function_name : str , sid : str , revision : str = " "
) - > None :
2020-01-23 18:46:24 +00:00
fn = self . get_function ( function_name )
2024-02-11 14:47:34 +00:00
fn . policy . del_statement ( sid , revision )
2020-01-23 18:46:24 +00:00
2022-10-22 11:40:20 +00:00
def get_code_signing_config ( self , function_name : str ) - > Dict [ str , Any ] :
2022-02-08 21:12:51 +00:00
fn = self . get_function ( function_name )
return fn . get_code_signing_config ( )
2023-11-06 23:54:23 +00:00
def get_policy ( self , function_name : str , qualifier : Optional [ str ] = None ) - > str :
2023-11-10 19:13:13 +00:00
fn = self . _lambdas . get_function_by_name_or_arn_with_qualifier (
function_name , qualifier
)
2024-02-11 14:47:34 +00:00
return fn . policy . wire_format ( )
2017-10-03 02:23:00 +00:00
2022-10-22 11:40:20 +00:00
def update_function_code (
self , function_name : str , qualifier : str , body : Dict [ str , Any ]
) - > Optional [ Dict [ str , Any ] ] :
fn : LambdaFunction = self . get_function ( function_name , qualifier )
2022-12-05 10:31:20 +00:00
fn . update_function_code ( body )
2019-11-15 16:34:14 +00:00
2022-10-22 11:40:20 +00:00
if body . get ( " Publish " , False ) :
fn = self . publish_function ( function_name ) # type: ignore[assignment]
2019-11-15 16:34:14 +00:00
2022-10-22 11:40:20 +00:00
return fn . update_function_code ( body )
2019-11-15 16:34:14 +00:00
2022-10-22 11:40:20 +00:00
def update_function_configuration (
self , function_name : str , qualifier : str , body : Dict [ str , Any ]
) - > Optional [ Dict [ str , Any ] ] :
2019-11-15 16:34:14 +00:00
fn = self . get_function ( function_name , qualifier )
2023-11-10 19:13:13 +00:00
return fn . update_configuration ( body )
2019-11-15 16:34:14 +00:00
2022-10-22 11:40:20 +00:00
def invoke (
self ,
function_name : str ,
2024-01-07 12:03:33 +00:00
qualifier : Optional [ str ] ,
2022-10-22 11:40:20 +00:00
body : Any ,
headers : Any ,
response_headers : Any ,
) - > Optional [ Union [ str , bytes ] ] :
2022-06-10 14:09:13 +00:00
"""
Invoking a Function with PackageType = Image is not yet supported .
"""
2019-11-15 16:34:14 +00:00
fn = self . get_function ( function_name , qualifier )
2023-11-10 19:13:13 +00:00
payload = fn . invoke ( body , headers , response_headers )
response_headers [ " Content-Length " ] = str ( len ( payload ) )
return payload
2019-11-15 16:34:14 +00:00
2022-10-22 11:40:20 +00:00
def put_function_concurrency (
self , function_name : str , reserved_concurrency : str
) - > str :
2023-08-15 16:43:40 +00:00
""" Establish concurrency limit/reservations for a function
Actual lambda restricts concurrency to 1000 ( default ) per region / account
across all functions ; we approximate that behavior by summing across all
functions ( hopefully all in the same account and region ) and allowing the
caller to simulate an increased quota .
By default , no quota is enforced in order to preserve compatibility with
existing code that assumes it can do as many things as it likes . To model
actual AWS behavior , define the MOTO_LAMBDA_CONCURRENCY_QUOTA environment
variable prior to testing .
"""
quota : Optional [ str ] = os . environ . get ( " MOTO_LAMBDA_CONCURRENCY_QUOTA " )
if quota is not None :
# Enforce concurrency limits as described above
available = int ( quota ) - int ( reserved_concurrency )
for fnx in self . list_functions ( ) :
if fnx . reserved_concurrency and fnx . function_name != function_name :
available - = int ( fnx . reserved_concurrency )
if available < 100 :
raise InvalidParameterValueException (
" Specified ReservedConcurrentExecutions for function decreases account ' s UnreservedConcurrentExecution below its minimum value of [100]. "
)
2020-08-26 10:06:53 +00:00
fn = self . get_function ( function_name )
fn . reserved_concurrency = reserved_concurrency
return fn . reserved_concurrency
2022-10-22 11:40:20 +00:00
def delete_function_concurrency ( self , function_name : str ) - > Optional [ str ] :
2020-08-26 10:06:53 +00:00
fn = self . get_function ( function_name )
fn . reserved_concurrency = None
return fn . reserved_concurrency
2022-10-22 11:40:20 +00:00
def get_function_concurrency ( self , function_name : str ) - > str :
2020-08-26 10:06:53 +00:00
fn = self . get_function ( function_name )
return fn . reserved_concurrency
2023-12-01 23:07:52 +00:00
def put_function_event_invoke_config (
self , function_name : str , config : Dict [ str , Any ]
) - > Dict [ str , Any ] :
fn = self . get_function ( function_name )
event_config = EventInvokeConfig ( fn . function_arn , fn . last_modified , config )
fn . event_invoke_config . append ( event_config )
return event_config . response ( )
def update_function_event_invoke_config (
self , function_name : str , config : Dict [ str , Any ]
) - > Dict [ str , Any ] :
# partial functionality, the update function just does a put
# instead of partial update
return self . put_function_event_invoke_config ( function_name , config )
def get_function_event_invoke_config ( self , function_name : str ) - > Dict [ str , Any ] :
fn = self . get_function ( function_name )
if fn . event_invoke_config :
response = fn . event_invoke_config [ 0 ]
return response . response ( )
else :
raise UnknownEventConfig ( fn . function_arn )
def delete_function_event_invoke_config ( self , function_name : str ) - > None :
if self . get_function_event_invoke_config ( function_name ) :
fn = self . get_function ( function_name )
fn . event_invoke_config = [ ]
def list_function_event_invoke_configs ( self , function_name : str ) - > Dict [ str , Any ] :
response : Dict [ str , List [ Dict [ str , Any ] ] ] = { " FunctionEventInvokeConfigs " : [ ] }
try :
response [ " FunctionEventInvokeConfigs " ] = [
self . get_function_event_invoke_config ( function_name )
]
return response
except UnknownEventConfig :
return response
2016-02-12 19:39:20 +00:00
2022-10-22 11:40:20 +00:00
def do_validate_s3 ( ) - > bool :
2017-05-24 12:54:00 +00:00
return os . environ . get ( " VALIDATE_LAMBDA_S3 " , " " ) in [ " " , " 1 " , " true " ]
2022-10-22 11:40:20 +00:00
lambda_backends = BackendDict ( LambdaBackend , " lambda " )