Lambda: additional optional fields (#6483)

This commit is contained in:
rafcio19 2023-07-06 17:45:29 +01:00 committed by GitHub
parent c2f000d496
commit 5f1ccb298e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 211 additions and 4 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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
``` ```

View File

@ -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},
} }