Lambda: additional optional fields (#6483)
This commit is contained in:
parent
c2f000d496
commit
5f1ccb298e
@ -89,3 +89,9 @@ class UnknownPolicyException(LambdaClientError):
|
|||||||
"ResourceNotFoundException",
|
"ResourceNotFoundException",
|
||||||
"No policy is associated with the given resource.",
|
"No policy is associated with the given resource.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(LambdaClientError):
|
||||||
|
def __init__(self, value: str, property_name: str, specific_message: str):
|
||||||
|
message = f"1 validation error detected: Value '{value}' at '{property_name}' failed to satisfy constraint: {specific_message}"
|
||||||
|
super().__init__("ValidationException", message)
|
||||||
|
@ -45,6 +45,7 @@ from .exceptions import (
|
|||||||
UnknownLayerVersionException,
|
UnknownLayerVersionException,
|
||||||
UnknownFunctionException,
|
UnknownFunctionException,
|
||||||
UnknownAliasException,
|
UnknownAliasException,
|
||||||
|
ValidationException,
|
||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
make_function_arn,
|
make_function_arn,
|
||||||
@ -196,6 +197,22 @@ def _validate_s3_bucket_and_key(
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
class ImageConfig:
|
||||||
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
|
self.cmd = config.get("Command", [])
|
||||||
|
self.entry_point = config.get("EntryPoint", [])
|
||||||
|
self.working_directory = config.get("WorkingDirectory", "")
|
||||||
|
|
||||||
|
def response(self) -> Dict[str, Any]:
|
||||||
|
return dict(
|
||||||
|
{
|
||||||
|
"Command": self.cmd,
|
||||||
|
"EntryPoint": self.entry_point,
|
||||||
|
"WorkingDirectory": self.working_directory,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Permission(CloudFormationModel):
|
class Permission(CloudFormationModel):
|
||||||
def __init__(self, region: str):
|
def __init__(self, region: str):
|
||||||
self.region = region
|
self.region = region
|
||||||
@ -429,6 +446,10 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
|||||||
self.reserved_concurrency = spec.get("ReservedConcurrentExecutions", None)
|
self.reserved_concurrency = spec.get("ReservedConcurrentExecutions", None)
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
|
self.ephemeral_storage: str
|
||||||
|
self.code_digest: str
|
||||||
|
self.code_bytes: bytes
|
||||||
|
|
||||||
self.description = spec.get("Description", "")
|
self.description = spec.get("Description", "")
|
||||||
self.memory_size = spec.get("MemorySize", 128)
|
self.memory_size = spec.get("MemorySize", 128)
|
||||||
self.package_type = spec.get("PackageType", None)
|
self.package_type = spec.get("PackageType", None)
|
||||||
@ -439,6 +460,13 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
|||||||
self.signing_job_arn = spec.get("SigningJobArn")
|
self.signing_job_arn = spec.get("SigningJobArn")
|
||||||
self.code_signing_config_arn = spec.get("CodeSigningConfigArn")
|
self.code_signing_config_arn = spec.get("CodeSigningConfigArn")
|
||||||
self.tracing_config = spec.get("TracingConfig") or {"Mode": "PassThrough"}
|
self.tracing_config = spec.get("TracingConfig") or {"Mode": "PassThrough"}
|
||||||
|
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
|
||||||
|
|
||||||
self.logs_group_name = f"/aws/lambda/{self.function_name}"
|
self.logs_group_name = f"/aws/lambda/{self.function_name}"
|
||||||
|
|
||||||
@ -531,6 +559,41 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
|||||||
datetime.datetime.utcnow()
|
datetime.datetime.utcnow()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vpc_config(self) -> Dict[str, Any]: # type: ignore[misc]
|
def vpc_config(self) -> Dict[str, Any]: # type: ignore[misc]
|
||||||
config = self._vpc_config.copy()
|
config = self._vpc_config.copy()
|
||||||
@ -582,7 +645,16 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
|||||||
"SigningProfileVersionArn": self.signing_profile_version_arn,
|
"SigningProfileVersionArn": self.signing_profile_version_arn,
|
||||||
"SigningJobArn": self.signing_job_arn,
|
"SigningJobArn": self.signing_job_arn,
|
||||||
"TracingConfig": self.tracing_config,
|
"TracingConfig": self.tracing_config,
|
||||||
|
"Architectures": self.architectures,
|
||||||
|
"EphemeralStorage": {
|
||||||
|
"Size": self.ephemeral_storage,
|
||||||
|
},
|
||||||
|
"SnapStart": {"ApplyOn": "None", "OptimizationStatus": "Off"},
|
||||||
}
|
}
|
||||||
|
if self.package_type == "Image":
|
||||||
|
config["ImageConfigResponse"] = {
|
||||||
|
"ImageConfig": self.image_config.response(),
|
||||||
|
}
|
||||||
if not on_create:
|
if not on_create:
|
||||||
# Only return this variable after the first creation
|
# Only return this variable after the first creation
|
||||||
config["LastUpdateStatus"] = "Successful"
|
config["LastUpdateStatus"] = "Successful"
|
||||||
|
@ -2,23 +2,33 @@ Documentation on how to run Terraform Tests can be found here:
|
|||||||
|
|
||||||
http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html#terraform-tests
|
http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html#terraform-tests
|
||||||
|
|
||||||
|
To get started you need to have [Go](https://go.dev/doc/install) installed.
|
||||||
|
|
||||||
|
One time setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod init moto
|
||||||
|
```
|
||||||
|
|
||||||
To see a list of available tests:
|
To see a list of available tests:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
cd tests/terraformtests/terraform-provider-aws
|
cd tests/terraformtests/terraform-provider-aws
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
go test ./internal/service/elb/ -v -list TestAcc
|
go test ./internal/service/elb/ -v -list TestAcc
|
||||||
```
|
```
|
||||||
|
|
||||||
To run a specific test:
|
To run a specific test:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
moto_server -p 4566
|
moto_server -p 4566
|
||||||
make terraformtests SERVICE_NAME=elb TEST_NAMES=NewTestName
|
make terraformtests SERVICE_NAME=elb TEST_NAMES=NewTestName
|
||||||
```
|
```
|
||||||
|
|
||||||
To see the list of tests that currently pass:
|
To see the list of tests that currently pass:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
python tests/terraformtests/get_tf_services.py
|
python tests/terraformtests/get_tf_services.py
|
||||||
python tests/terraformtests/get_tf_tests.py ec2
|
python tests/terraformtests/get_tf_tests.py ec2
|
||||||
```
|
```
|
||||||
|
@ -6,7 +6,7 @@ import boto3
|
|||||||
import hashlib
|
import hashlib
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, ParamValidationError
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
from tests.test_ecr.test_ecr_helpers import _create_image_manifest
|
from tests.test_ecr.test_ecr_helpers import _create_image_manifest
|
||||||
from moto import mock_lambda, mock_s3, mock_ecr, settings
|
from moto import mock_lambda, mock_s3, mock_ecr, settings
|
||||||
@ -172,6 +172,8 @@ def test_create_function_from_zipfile():
|
|||||||
result.pop("LastModified")
|
result.pop("LastModified")
|
||||||
|
|
||||||
assert result == {
|
assert result == {
|
||||||
|
"Architectures": ["x86_64"],
|
||||||
|
"EphemeralStorage": {"Size": 512},
|
||||||
"FunctionName": function_name,
|
"FunctionName": function_name,
|
||||||
"FunctionArn": f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}",
|
"FunctionArn": f"arn:aws:lambda:{_lambda_region}:{ACCOUNT_ID}:function:{function_name}",
|
||||||
"Runtime": "python2.7",
|
"Runtime": "python2.7",
|
||||||
@ -190,9 +192,122 @@ def test_create_function_from_zipfile():
|
|||||||
"State": "Active",
|
"State": "Active",
|
||||||
"Layers": [],
|
"Layers": [],
|
||||||
"TracingConfig": {"Mode": "PassThrough"},
|
"TracingConfig": {"Mode": "PassThrough"},
|
||||||
|
"SnapStart": {"ApplyOn": "None", "OptimizationStatus": "Off"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lambda
|
||||||
|
def test_create_function_from_image():
|
||||||
|
conn = boto3.client("lambda", _lambda_region)
|
||||||
|
function_name = str(uuid4())[0:6]
|
||||||
|
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:prod"
|
||||||
|
image_config = {
|
||||||
|
"EntryPoint": [
|
||||||
|
"python",
|
||||||
|
],
|
||||||
|
"Command": [
|
||||||
|
"/opt/app.py",
|
||||||
|
],
|
||||||
|
"WorkingDirectory": "/opt",
|
||||||
|
}
|
||||||
|
conn.create_function(
|
||||||
|
FunctionName=function_name,
|
||||||
|
Role=get_role_name(),
|
||||||
|
Code={"ImageUri": image_uri},
|
||||||
|
Description="test lambda function",
|
||||||
|
ImageConfig=image_config,
|
||||||
|
PackageType="Image",
|
||||||
|
Timeout=3,
|
||||||
|
MemorySize=128,
|
||||||
|
Publish=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = conn.get_function(FunctionName=function_name)
|
||||||
|
|
||||||
|
assert "ImageConfigResponse" in result["Configuration"]
|
||||||
|
assert result["Configuration"]["ImageConfigResponse"]["ImageConfig"] == image_config
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lambda
|
||||||
|
def test_create_function_error_bad_architecture():
|
||||||
|
conn = boto3.client("lambda", _lambda_region)
|
||||||
|
function_name = str(uuid4())[0:6]
|
||||||
|
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:prod"
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
conn.create_function(
|
||||||
|
Architectures=["foo"],
|
||||||
|
FunctionName=function_name,
|
||||||
|
Role=get_role_name(),
|
||||||
|
Code={"ImageUri": image_uri},
|
||||||
|
Description="test lambda function",
|
||||||
|
Timeout=3,
|
||||||
|
MemorySize=128,
|
||||||
|
Publish=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
err = exc.value.response
|
||||||
|
|
||||||
|
assert err["Error"]["Code"] == "ValidationException"
|
||||||
|
assert (
|
||||||
|
err["Error"]["Message"]
|
||||||
|
== "1 validation error detected: Value '['foo']' at 'architectures' failed to satisfy"
|
||||||
|
" constraint: Member must satisfy constraint: [Member must satisfy enum value set: "
|
||||||
|
"[x86_64, arm64], Member must not be null]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lambda
|
||||||
|
def test_create_function_error_ephemeral_too_big():
|
||||||
|
conn = boto3.client("lambda", _lambda_region)
|
||||||
|
function_name = str(uuid4())[0:6]
|
||||||
|
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:prod"
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
conn.create_function(
|
||||||
|
FunctionName=function_name,
|
||||||
|
Role=get_role_name(),
|
||||||
|
Code={"ImageUri": image_uri},
|
||||||
|
Description="test lambda function",
|
||||||
|
Timeout=3,
|
||||||
|
MemorySize=128,
|
||||||
|
Publish=True,
|
||||||
|
EphemeralStorage={"Size": 3000000},
|
||||||
|
)
|
||||||
|
|
||||||
|
err = exc.value.response
|
||||||
|
|
||||||
|
assert err["Error"]["Code"] == "ValidationException"
|
||||||
|
assert (
|
||||||
|
err["Error"]["Message"]
|
||||||
|
== "1 validation error detected: Value '3000000' at 'ephemeralStorage.size' "
|
||||||
|
"failed to satisfy constraint: "
|
||||||
|
"Member must have value less than or equal to 10240"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lambda
|
||||||
|
def test_create_function_error_ephemeral_too_small():
|
||||||
|
conn = boto3.client("lambda", _lambda_region)
|
||||||
|
function_name = str(uuid4())[0:6]
|
||||||
|
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:prod"
|
||||||
|
|
||||||
|
with pytest.raises(ParamValidationError) as exc:
|
||||||
|
conn.create_function(
|
||||||
|
FunctionName=function_name,
|
||||||
|
Role=get_role_name(),
|
||||||
|
Code={"ImageUri": image_uri},
|
||||||
|
Description="test lambda function",
|
||||||
|
Timeout=3,
|
||||||
|
MemorySize=128,
|
||||||
|
Publish=True,
|
||||||
|
EphemeralStorage={"Size": 200},
|
||||||
|
)
|
||||||
|
|
||||||
|
# this one is handled by botocore, not moto
|
||||||
|
assert exc.typename == "ParamValidationError"
|
||||||
|
|
||||||
|
|
||||||
@mock_lambda
|
@mock_lambda
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"tracing_mode",
|
"tracing_mode",
|
||||||
@ -762,6 +877,7 @@ def test_list_create_list_get_delete_list():
|
|||||||
Handler="lambda_function.lambda_handler",
|
Handler="lambda_function.lambda_handler",
|
||||||
Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
|
Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
|
||||||
Description="test lambda function",
|
Description="test lambda function",
|
||||||
|
EphemeralStorage={"Size": 2500},
|
||||||
Timeout=3,
|
Timeout=3,
|
||||||
MemorySize=128,
|
MemorySize=128,
|
||||||
Publish=True,
|
Publish=True,
|
||||||
@ -789,6 +905,9 @@ def test_list_create_list_get_delete_list():
|
|||||||
"Layers": [],
|
"Layers": [],
|
||||||
"LastUpdateStatus": "Successful",
|
"LastUpdateStatus": "Successful",
|
||||||
"TracingConfig": {"Mode": "PassThrough"},
|
"TracingConfig": {"Mode": "PassThrough"},
|
||||||
|
"Architectures": ["x86_64"],
|
||||||
|
"EphemeralStorage": {"Size": 2500},
|
||||||
|
"SnapStart": {"ApplyOn": "None", "OptimizationStatus": "Off"},
|
||||||
},
|
},
|
||||||
"ResponseMetadata": {"HTTPStatusCode": 200},
|
"ResponseMetadata": {"HTTPStatusCode": 200},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user