From b88c5454e28f28e24c0fb40346749cd73392108e Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Sat, 27 May 2023 12:49:32 +0300 Subject: [PATCH 01/16] feat(parser): Parser support for custom resource --- .../utilities/parser/models/__init__.py | 10 ++ .../parser/models/custom_resource.py | 27 ++++ docs/utilities/parser.md | 3 + tests/events/customResourceCreate.json | 13 ++ tests/events/customResourceDelete.json | 13 ++ tests/events/customResourceUpdate.json | 17 +++ .../functional/parser/test_custom_resource.py | 130 ++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 aws_lambda_powertools/utilities/parser/models/custom_resource.py create mode 100644 tests/events/customResourceCreate.json create mode 100644 tests/events/customResourceDelete.json create mode 100644 tests/events/customResourceUpdate.json create mode 100644 tests/functional/parser/test_custom_resource.py diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index c2385b7bf14..613a44b119e 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -20,6 +20,12 @@ CloudWatchLogsLogEvent, CloudWatchLogsModel, ) +from .custom_resource import ( + CustomResourceBaseModel, + CustomResourceCreateModel, + CustomResourceDeleteModel, + CustomResourceUpdateModel, +) from .dynamodb import ( DynamoDBStreamChangedRecordModel, DynamoDBStreamModel, @@ -147,4 +153,8 @@ "KafkaBaseEventModel", "KinesisFirehoseSqsModel", "KinesisFirehoseSqsRecord", + "CustomResourceUpdateModel", + "CustomResourceDeleteModel", + "CustomResourceCreateModel", + "CustomResourceBaseModel", ] diff --git a/aws_lambda_powertools/utilities/parser/models/custom_resource.py b/aws_lambda_powertools/utilities/parser/models/custom_resource.py new file mode 100644 index 00000000000..ba9e050493d --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/models/custom_resource.py @@ -0,0 +1,27 @@ +from typing import Any, Dict, Literal, Type, Union + +from pydantic import BaseModel, Field, HttpUrl + + +class CustomResourceBaseModel(BaseModel): + request_type: str = Field(..., alias="RequestType") + service_token: str = Field(..., alias="ServiceToken") + response_url: HttpUrl = Field(..., alias="ResponseURL") + stack_id: str = Field(..., alias="StackId") + request_id: str = Field(..., alias="RequestId") + logical_resource_id: str = Field(..., alias="LogicalResourceId") + resource_type: str = Field(..., alias="ResourceType") + resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="ResourceProperties") + + +class CustomResourceCreateModel(CustomResourceBaseModel): + request_type: Literal["Create"] = Field(..., alias="RequestType") + + +class CustomResourceDeleteModel(CustomResourceBaseModel): + request_type: Literal["Delete"] = Field(..., alias="RequestType") + + +class CustomResourceUpdateModel(CustomResourceBaseModel): + request_type: Literal["Update"] = Field(..., alias="RequestType") + old_resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="OldResourceProperties") diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 6607e7b07b0..5b57d86572f 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -177,6 +177,9 @@ Parser comes with the following built-in models: | **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | | **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | | **SqsModel** | Lambda Event Source payload for Amazon SQS | +| **CustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | +| **CustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | +| **CustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | #### Extending built-in models diff --git a/tests/events/customResourceCreate.json b/tests/events/customResourceCreate.json new file mode 100644 index 00000000000..5c32d8c7aa1 --- /dev/null +++ b/tests/events/customResourceCreate.json @@ -0,0 +1,13 @@ +{ + "RequestType": "Create", + "ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe", + "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b", + "StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21", + "RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx", + "LogicalResourceId": "xxxxxxxxx", + "ResourceType": "Custom::MyType", + "ResourceProperties": { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "ss" + } +} \ No newline at end of file diff --git a/tests/events/customResourceDelete.json b/tests/events/customResourceDelete.json new file mode 100644 index 00000000000..f26738133db --- /dev/null +++ b/tests/events/customResourceDelete.json @@ -0,0 +1,13 @@ +{ + "RequestType": "Delete", + "ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe", + "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b", + "StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21", + "RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx", + "LogicalResourceId": "xxxxxxxxx", + "ResourceType": "Custom::MyType", + "ResourceProperties": { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "ss" + } +} \ No newline at end of file diff --git a/tests/events/customResourceUpdate.json b/tests/events/customResourceUpdate.json new file mode 100644 index 00000000000..52257463455 --- /dev/null +++ b/tests/events/customResourceUpdate.json @@ -0,0 +1,17 @@ +{ + "RequestType": "Update", + "ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe", + "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b", + "StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21", + "RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx", + "LogicalResourceId": "xxxxxxxxx", + "ResourceType": "Custom::MyType", + "ResourceProperties": { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "new" + }, + "OldResourceProperties": { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx-xxxx-xxx", + "MyProps": "old" + } +} \ No newline at end of file diff --git a/tests/functional/parser/test_custom_resource.py b/tests/functional/parser/test_custom_resource.py new file mode 100644 index 00000000000..eaf6d87e34c --- /dev/null +++ b/tests/functional/parser/test_custom_resource.py @@ -0,0 +1,130 @@ +import pytest +from pydantic import BaseModel, Field + +from aws_lambda_powertools.utilities.parser import ValidationError, event_parser +from aws_lambda_powertools.utilities.parser.models import ( + CustomResourceCreateModel, + CustomResourceDeleteModel, + CustomResourceUpdateModel, +) +from aws_lambda_powertools.utilities.typing import LambdaContext +from tests.functional.utils import load_event + + +@event_parser(model=CustomResourceCreateModel) +def handle_create_custom_resource(event: CustomResourceCreateModel, _: LambdaContext): + assert event.request_type == "Create" + assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" + assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" + assert ( + str(event.response_url) + == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" + ) + assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" + assert event.logical_resource_id == "xxxxxxxxx" + assert event.resource_type == "Custom::MyType" + assert event.resource_properties == { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "ss", + } + + +@event_parser(model=CustomResourceUpdateModel) +def handle_update_custom_resource(event: CustomResourceUpdateModel, _: LambdaContext): + assert event.request_type == "Update" + assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" + assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" + assert ( + str(event.response_url) + == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" + ) + assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" + assert event.logical_resource_id == "xxxxxxxxx" + assert event.resource_type == "Custom::MyType" + assert event.resource_properties == { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "new", + } + assert event.old_resource_properties == { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx-xxxx-xxx", + "MyProps": "old", + } + + +@event_parser(model=CustomResourceDeleteModel) +def handle_delete_custom_resource(event: CustomResourceDeleteModel, _: LambdaContext): + assert event.request_type == "Delete" + assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" + assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" + assert ( + str(event.response_url) + == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" + ) + assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" + assert event.logical_resource_id == "xxxxxxxxx" + assert event.resource_type == "Custom::MyType" + assert event.resource_properties == { + "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", + "MyProps": "ss", + } + + +def test_create_trigger_event(): + event_dict = load_event("customResourceCreate.json") + handle_create_custom_resource(event_dict, LambdaContext()) + + +def test_update_trigger_event(): + event_dict = load_event("customResourceUpdate.json") + handle_update_custom_resource(event_dict, LambdaContext()) + + +def test_delete_trigger_event(): + event_dict = load_event("customResourceDelete.json") + handle_delete_custom_resource(event_dict, LambdaContext()) + + +def test_validate_create_event_does_not_conform_with_model(): + event = {"invalid": "event"} + with pytest.raises(ValidationError): + handle_create_custom_resource(event, LambdaContext()) + + +def test_validate_update_event_does_not_conform_with_model(): + event = {"invalid": "event"} + with pytest.raises(ValidationError): + handle_update_custom_resource(event, LambdaContext()) + + +def test_validate_delete_event_does_not_conform_with_model(): + event = {"invalid": "event"} + with pytest.raises(ValidationError): + handle_delete_custom_resource(event, LambdaContext()) + + +class MyModel(BaseModel): + MyProps: str + + +class MyCustomResource(CustomResourceCreateModel): + resource_properties: MyModel = Field(..., alias="ResourceProperties") + + +@event_parser(model=MyCustomResource) +def handle_create_custom_resource_extended_model(event: MyCustomResource, _: LambdaContext): + assert event.request_type == "Create" + assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" + assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" + assert ( + str(event.response_url) + == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" + ) + assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" + assert event.logical_resource_id == "xxxxxxxxx" + assert event.resource_type == "Custom::MyType" + assert event.resource_properties.MyProps == "ss" + + +def test_create_trigger_event_custom_model(): + event_dict = load_event("customResourceCreate.json") + handle_create_custom_resource_extended_model(event_dict, LambdaContext()) From 68c75ea53d21926b3210bd7cb003d4d49a8650b9 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Mon, 29 May 2023 17:38:16 +0300 Subject: [PATCH 02/16] fix pipeline for older python --- .../utilities/parser/models/custom_resource.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/custom_resource.py b/aws_lambda_powertools/utilities/parser/models/custom_resource.py index ba9e050493d..e02bd51e649 100644 --- a/aws_lambda_powertools/utilities/parser/models/custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/custom_resource.py @@ -1,7 +1,9 @@ -from typing import Any, Dict, Literal, Type, Union +from typing import Any, Dict, Type, Union from pydantic import BaseModel, Field, HttpUrl +from aws_lambda_powertools.utilities.parser.types import Literal + class CustomResourceBaseModel(BaseModel): request_type: str = Field(..., alias="RequestType") From d9d5de49bb4fb83ad1dc2e487fee97b5ef8d0837 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Thu, 8 Jun 2023 14:17:38 +0300 Subject: [PATCH 03/16] rename to cloudformation --- .../utilities/parser/models/__init__.py | 20 ++++---- ...e.py => cloudformation_custom_resource.py} | 8 ++-- docs/utilities/parser.md | 48 +++++++++---------- .../functional/parser/test_custom_resource.py | 20 ++++---- 4 files changed, 48 insertions(+), 48 deletions(-) rename aws_lambda_powertools/utilities/parser/models/{custom_resource.py => cloudformation_custom_resource.py} (75%) diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 613a44b119e..952280a519c 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -14,18 +14,18 @@ RequestContextV2AuthorizerJwt, RequestContextV2Http, ) +from .cloudformation_custom_resource import ( + CloudFormationCustomResourceBaseModel, + CloudFormationCustomResourceCreateModel, + CloudFormationCustomResourceDeleteModel, + CloudFormationCustomResourceUpdateModel, +) from .cloudwatch import ( CloudWatchLogsData, CloudWatchLogsDecode, CloudWatchLogsLogEvent, CloudWatchLogsModel, ) -from .custom_resource import ( - CustomResourceBaseModel, - CustomResourceCreateModel, - CustomResourceDeleteModel, - CustomResourceUpdateModel, -) from .dynamodb import ( DynamoDBStreamChangedRecordModel, DynamoDBStreamModel, @@ -153,8 +153,8 @@ "KafkaBaseEventModel", "KinesisFirehoseSqsModel", "KinesisFirehoseSqsRecord", - "CustomResourceUpdateModel", - "CustomResourceDeleteModel", - "CustomResourceCreateModel", - "CustomResourceBaseModel", + "CloudFormationCustomResourceUpdateModel", + "CloudFormationCustomResourceDeleteModel", + "CloudFormationCustomResourceCreateModel", + "CloudFormationCustomResourceBaseModel", ] diff --git a/aws_lambda_powertools/utilities/parser/models/custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py similarity index 75% rename from aws_lambda_powertools/utilities/parser/models/custom_resource.py rename to aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index e02bd51e649..1e5f2e1c7e7 100644 --- a/aws_lambda_powertools/utilities/parser/models/custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -5,7 +5,7 @@ from aws_lambda_powertools.utilities.parser.types import Literal -class CustomResourceBaseModel(BaseModel): +class CloudFormationCustomResourceBaseModel(BaseModel): request_type: str = Field(..., alias="RequestType") service_token: str = Field(..., alias="ServiceToken") response_url: HttpUrl = Field(..., alias="ResponseURL") @@ -16,14 +16,14 @@ class CustomResourceBaseModel(BaseModel): resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="ResourceProperties") -class CustomResourceCreateModel(CustomResourceBaseModel): +class CloudFormationCustomResourceCreateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Create"] = Field(..., alias="RequestType") -class CustomResourceDeleteModel(CustomResourceBaseModel): +class CloudFormationCustomResourceDeleteModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Delete"] = Field(..., alias="RequestType") -class CustomResourceUpdateModel(CustomResourceBaseModel): +class CloudFormationCustomResourceUpdateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Update"] = Field(..., alias="RequestType") old_resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="OldResourceProperties") diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 5b57d86572f..cfb3fbc2eb4 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -156,30 +156,30 @@ def my_function(): Parser comes with the following built-in models: -| Model name | Description | -| --------------------------------------- | ------------------------------------------------------------------------------------- | -| **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | -| **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | -| **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | -| **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | -| **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | -| **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | -| **KafkaMskEventModel** | Lambda Event Source payload for AWS MSK payload | -| **KafkaSelfManagedEventModel** | Lambda Event Source payload for self managed Kafka payload | -| **KinesisDataStreamModel** | Lambda Event Source payload for Amazon Kinesis Data Streams | -| **KinesisFirehoseModel** | Lambda Event Source payload for Amazon Kinesis Firehose | -| **KinesisFirehoseSqsModel** | Lambda Event Source payload for SQS messages wrapped in Kinesis Firehose records | -| **LambdaFunctionUrlModel** | Lambda Event Source payload for Lambda Function URL payload | -| **S3EventNotificationEventBridgeModel** | Lambda Event Source payload for Amazon S3 Event Notification to EventBridge. | -| **S3Model** | Lambda Event Source payload for Amazon S3 | -| **S3ObjectLambdaEvent** | Lambda Event Source payload for Amazon S3 Object Lambda | -| **S3SqsEventNotificationModel** | Lambda Event Source payload for S3 event notifications wrapped in SQS event (S3->SQS) | -| **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | -| **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | -| **SqsModel** | Lambda Event Source payload for Amazon SQS | -| **CustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | -| **CustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | -| **CustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | +| Model name | Description | +| ------------------------------------------- | ------------------------------------------------------------------------------------- | +| **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | +| **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | +| **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | +| **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | +| **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | +| **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | +| **KafkaMskEventModel** | Lambda Event Source payload for AWS MSK payload | +| **KafkaSelfManagedEventModel** | Lambda Event Source payload for self managed Kafka payload | +| **KinesisDataStreamModel** | Lambda Event Source payload for Amazon Kinesis Data Streams | +| **KinesisFirehoseModel** | Lambda Event Source payload for Amazon Kinesis Firehose | +| **KinesisFirehoseSqsModel** | Lambda Event Source payload for SQS messages wrapped in Kinesis Firehose records | +| **LambdaFunctionUrlModel** | Lambda Event Source payload for Lambda Function URL payload | +| **S3EventNotificationEventBridgeModel** | Lambda Event Source payload for Amazon S3 Event Notification to EventBridge. | +| **S3Model** | Lambda Event Source payload for Amazon S3 | +| **S3ObjectLambdaEvent** | Lambda Event Source payload for Amazon S3 Object Lambda | +| **S3SqsEventNotificationModel** | Lambda Event Source payload for S3 event notifications wrapped in SQS event (S3->SQS) | +| **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | +| **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | +| **SqsModel** | Lambda Event Source payload for Amazon SQS | +| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | +| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | +| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | #### Extending built-in models diff --git a/tests/functional/parser/test_custom_resource.py b/tests/functional/parser/test_custom_resource.py index eaf6d87e34c..aea62b7aa42 100644 --- a/tests/functional/parser/test_custom_resource.py +++ b/tests/functional/parser/test_custom_resource.py @@ -3,16 +3,16 @@ from aws_lambda_powertools.utilities.parser import ValidationError, event_parser from aws_lambda_powertools.utilities.parser.models import ( - CustomResourceCreateModel, - CustomResourceDeleteModel, - CustomResourceUpdateModel, + CloudFormationCustomResourceCreateModel, + CloudFormationCustomResourceDeleteModel, + CloudFormationCustomResourceUpdateModel, ) from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event -@event_parser(model=CustomResourceCreateModel) -def handle_create_custom_resource(event: CustomResourceCreateModel, _: LambdaContext): +@event_parser(model=CloudFormationCustomResourceCreateModel) +def handle_create_custom_resource(event: CloudFormationCustomResourceCreateModel, _: LambdaContext): assert event.request_type == "Create" assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" @@ -29,8 +29,8 @@ def handle_create_custom_resource(event: CustomResourceCreateModel, _: LambdaCon } -@event_parser(model=CustomResourceUpdateModel) -def handle_update_custom_resource(event: CustomResourceUpdateModel, _: LambdaContext): +@event_parser(model=CloudFormationCustomResourceUpdateModel) +def handle_update_custom_resource(event: CloudFormationCustomResourceUpdateModel, _: LambdaContext): assert event.request_type == "Update" assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" @@ -51,8 +51,8 @@ def handle_update_custom_resource(event: CustomResourceUpdateModel, _: LambdaCon } -@event_parser(model=CustomResourceDeleteModel) -def handle_delete_custom_resource(event: CustomResourceDeleteModel, _: LambdaContext): +@event_parser(model=CloudFormationCustomResourceDeleteModel) +def handle_delete_custom_resource(event: CloudFormationCustomResourceDeleteModel, _: LambdaContext): assert event.request_type == "Delete" assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" @@ -106,7 +106,7 @@ class MyModel(BaseModel): MyProps: str -class MyCustomResource(CustomResourceCreateModel): +class MyCustomResource(CloudFormationCustomResourceCreateModel): resource_properties: MyModel = Field(..., alias="ResourceProperties") From 175b141ddc7b96e6e07add24b1c1a67bed25dbcf Mon Sep 17 00:00:00 2001 From: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:05:29 +0300 Subject: [PATCH 04/16] Update aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py Co-authored-by: Leandro Damascena Signed-off-by: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> --- .../utilities/parser/models/cloudformation_custom_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index 1e5f2e1c7e7..c8dc0d21d1f 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -13,7 +13,7 @@ class CloudFormationCustomResourceBaseModel(BaseModel): request_id: str = Field(..., alias="RequestId") logical_resource_id: str = Field(..., alias="LogicalResourceId") resource_type: str = Field(..., alias="ResourceType") - resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="ResourceProperties") + resource_properties: Dict[str, Any] | Type[BaseModel] | None = Field(None, alias="ResourceProperties") class CloudFormationCustomResourceCreateModel(CloudFormationCustomResourceBaseModel): From bcf6ff8038f297d4ce4b8593a6f1104f0405386e Mon Sep 17 00:00:00 2001 From: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:06:09 +0300 Subject: [PATCH 05/16] Update aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py Co-authored-by: Leandro Damascena Signed-off-by: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> --- .../utilities/parser/models/cloudformation_custom_resource.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index c8dc0d21d1f..07743f6f5d3 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Type, Union +from __future__ import annotations + +from typing import Any, Dict, Type from pydantic import BaseModel, Field, HttpUrl From 5dc7b1c93d07520ee472ca249222b1b31dcf07a5 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 9 Jun 2023 15:11:54 +0300 Subject: [PATCH 06/16] cr fixes --- .../parser/models/cloudformation_custom_resource.py | 2 +- docs/utilities/parser.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index 07743f6f5d3..802444eb000 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -28,4 +28,4 @@ class CloudFormationCustomResourceDeleteModel(CloudFormationCustomResourceBaseMo class CloudFormationCustomResourceUpdateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Update"] = Field(..., alias="RequestType") - old_resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="OldResourceProperties") + old_resource_properties: Dict[str, Any] | Type[BaseModel] = Field(..., alias="OldResourceProperties") diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index fcd23cc3b1d..e2f9b0a2013 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -161,6 +161,9 @@ Parser comes with the following built-in models: | **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | | **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | | **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | +| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | +| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | +| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | | **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | | **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | | **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | @@ -177,9 +180,6 @@ Parser comes with the following built-in models: | **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | | **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | | **SqsModel** | Lambda Event Source payload for Amazon SQS | -| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | -| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | -| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | #### Extending built-in models From 6b4ea785b5be3fd0972c1986f72c825101b4cfa7 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 9 Jun 2023 16:11:28 +0300 Subject: [PATCH 07/16] optional old resource props --- .../utilities/parser/models/cloudformation_custom_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index 802444eb000..9f7bffc3e9a 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -28,4 +28,4 @@ class CloudFormationCustomResourceDeleteModel(CloudFormationCustomResourceBaseMo class CloudFormationCustomResourceUpdateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Update"] = Field(..., alias="RequestType") - old_resource_properties: Dict[str, Any] | Type[BaseModel] = Field(..., alias="OldResourceProperties") + old_resource_properties: Dict[str, Any] | Type[BaseModel] | None = Field(None, alias="OldResourceProperties") From 51fc1ef5cf0178e2e9ed4fe059919394fdf9232f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:03:59 +0200 Subject: [PATCH 08/16] revert: union type due to python bug --- .../parser/models/cloudformation_custom_resource.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index 9f7bffc3e9a..8be4ca41ca8 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -1,6 +1,4 @@ -from __future__ import annotations - -from typing import Any, Dict, Type +from typing import Any, Dict, Type, Union from pydantic import BaseModel, Field, HttpUrl @@ -15,7 +13,7 @@ class CloudFormationCustomResourceBaseModel(BaseModel): request_id: str = Field(..., alias="RequestId") logical_resource_id: str = Field(..., alias="LogicalResourceId") resource_type: str = Field(..., alias="ResourceType") - resource_properties: Dict[str, Any] | Type[BaseModel] | None = Field(None, alias="ResourceProperties") + resource_properties: Union[Dict[str, Any], Type[BaseModel], None] = Field(None, alias="ResourceProperties") class CloudFormationCustomResourceCreateModel(CloudFormationCustomResourceBaseModel): @@ -28,4 +26,4 @@ class CloudFormationCustomResourceDeleteModel(CloudFormationCustomResourceBaseMo class CloudFormationCustomResourceUpdateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Update"] = Field(..., alias="RequestType") - old_resource_properties: Dict[str, Any] | Type[BaseModel] | None = Field(None, alias="OldResourceProperties") + old_resource_properties: Union[Dict[str, Any], Type[BaseModel], None] = Field(None, alias="OldResourceProperties") From e58c4033129de042d409623bed6296f2c6494e80 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:06:50 +0200 Subject: [PATCH 09/16] docs: improve description for cfn custom resource model --- docs/utilities/parser.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index e2f9b0a2013..0e25f9441a4 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -161,9 +161,9 @@ Parser comes with the following built-in models: | **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | | **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | | **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | -| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource | -| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource | -| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource | +| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | +| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | +| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation `DELETE` operation | | **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | | **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | | **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | From 0b09a6db2e510b5f247e22695788b39dbaab4c32 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:13:56 +0200 Subject: [PATCH 10/16] chore: rename test event --- ...reate.json => cloudformationCustomResourceCreate.json} | 0 ...elete.json => cloudformationCustomResourceDelete.json} | 0 ...pdate.json => cloudformationCustomResourceUpdate.json} | 0 tests/functional/parser/test_custom_resource.py | 8 ++++---- 4 files changed, 4 insertions(+), 4 deletions(-) rename tests/events/{customResourceCreate.json => cloudformationCustomResourceCreate.json} (100%) rename tests/events/{customResourceDelete.json => cloudformationCustomResourceDelete.json} (100%) rename tests/events/{customResourceUpdate.json => cloudformationCustomResourceUpdate.json} (100%) diff --git a/tests/events/customResourceCreate.json b/tests/events/cloudformationCustomResourceCreate.json similarity index 100% rename from tests/events/customResourceCreate.json rename to tests/events/cloudformationCustomResourceCreate.json diff --git a/tests/events/customResourceDelete.json b/tests/events/cloudformationCustomResourceDelete.json similarity index 100% rename from tests/events/customResourceDelete.json rename to tests/events/cloudformationCustomResourceDelete.json diff --git a/tests/events/customResourceUpdate.json b/tests/events/cloudformationCustomResourceUpdate.json similarity index 100% rename from tests/events/customResourceUpdate.json rename to tests/events/cloudformationCustomResourceUpdate.json diff --git a/tests/functional/parser/test_custom_resource.py b/tests/functional/parser/test_custom_resource.py index aea62b7aa42..320da1e5f2e 100644 --- a/tests/functional/parser/test_custom_resource.py +++ b/tests/functional/parser/test_custom_resource.py @@ -70,17 +70,17 @@ def handle_delete_custom_resource(event: CloudFormationCustomResourceDeleteModel def test_create_trigger_event(): - event_dict = load_event("customResourceCreate.json") + event_dict = load_event("cloudformationCustomResourceCreate.json") handle_create_custom_resource(event_dict, LambdaContext()) def test_update_trigger_event(): - event_dict = load_event("customResourceUpdate.json") + event_dict = load_event("cloudformationCustomResourceUpdate.json") handle_update_custom_resource(event_dict, LambdaContext()) def test_delete_trigger_event(): - event_dict = load_event("customResourceDelete.json") + event_dict = load_event("cloudformationCustomResourceDelete.json") handle_delete_custom_resource(event_dict, LambdaContext()) @@ -126,5 +126,5 @@ def handle_create_custom_resource_extended_model(event: MyCustomResource, _: Lam def test_create_trigger_event_custom_model(): - event_dict = load_event("customResourceCreate.json") + event_dict = load_event("cloudformationCustomResourceCreate.json") handle_create_custom_resource_extended_model(event_dict, LambdaContext()) From 045f97cc9ff0d34d6bbebf53f38f8740f4777868 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:16:01 +0200 Subject: [PATCH 11/16] chore: rename test and move to unit --- .../parser/test_cloudformation_custom_resource.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{functional/parser/test_custom_resource.py => unit/parser/test_cloudformation_custom_resource.py} (100%) diff --git a/tests/functional/parser/test_custom_resource.py b/tests/unit/parser/test_cloudformation_custom_resource.py similarity index 100% rename from tests/functional/parser/test_custom_resource.py rename to tests/unit/parser/test_cloudformation_custom_resource.py From caf218a63051dda9e0f108b206941958336ab1e7 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:31:00 +0200 Subject: [PATCH 12/16] chore: refactor create test as unit test --- .../test_cloudformation_custom_resource.py | 80 ++++++++----------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/tests/unit/parser/test_cloudformation_custom_resource.py b/tests/unit/parser/test_cloudformation_custom_resource.py index 320da1e5f2e..b3e3b8021bb 100644 --- a/tests/unit/parser/test_cloudformation_custom_resource.py +++ b/tests/unit/parser/test_cloudformation_custom_resource.py @@ -11,24 +11,6 @@ from tests.functional.utils import load_event -@event_parser(model=CloudFormationCustomResourceCreateModel) -def handle_create_custom_resource(event: CloudFormationCustomResourceCreateModel, _: LambdaContext): - assert event.request_type == "Create" - assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" - assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" - assert ( - str(event.response_url) - == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" - ) - assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" - assert event.logical_resource_id == "xxxxxxxxx" - assert event.resource_type == "Custom::MyType" - assert event.resource_properties == { - "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", - "MyProps": "ss", - } - - @event_parser(model=CloudFormationCustomResourceUpdateModel) def handle_update_custom_resource(event: CloudFormationCustomResourceUpdateModel, _: LambdaContext): assert event.request_type == "Update" @@ -69,11 +51,6 @@ def handle_delete_custom_resource(event: CloudFormationCustomResourceDeleteModel } -def test_create_trigger_event(): - event_dict = load_event("cloudformationCustomResourceCreate.json") - handle_create_custom_resource(event_dict, LambdaContext()) - - def test_update_trigger_event(): event_dict = load_event("cloudformationCustomResourceUpdate.json") handle_update_custom_resource(event_dict, LambdaContext()) @@ -84,12 +61,6 @@ def test_delete_trigger_event(): handle_delete_custom_resource(event_dict, LambdaContext()) -def test_validate_create_event_does_not_conform_with_model(): - event = {"invalid": "event"} - with pytest.raises(ValidationError): - handle_create_custom_resource(event, LambdaContext()) - - def test_validate_update_event_does_not_conform_with_model(): event = {"invalid": "event"} with pytest.raises(ValidationError): @@ -106,25 +77,40 @@ class MyModel(BaseModel): MyProps: str -class MyCustomResource(CloudFormationCustomResourceCreateModel): - resource_properties: MyModel = Field(..., alias="ResourceProperties") +def test_cloudformation_custom_resource_create_event_custom_model(): + class MyCustomResource(CloudFormationCustomResourceCreateModel): + resource_properties: MyModel = Field(..., alias="ResourceProperties") + raw_event = load_event("cloudformationCustomResourceCreate.json") + model = MyCustomResource(**raw_event) + + assert model.request_type == raw_event["RequestType"] + assert model.request_id == raw_event["RequestId"] + assert model.service_token == raw_event["ServiceToken"] + assert str(model.response_url) == raw_event["ResponseURL"] + assert model.stack_id == raw_event["StackId"] + assert model.logical_resource_id == raw_event["LogicalResourceId"] + assert model.resource_type == raw_event["ResourceType"] + assert model.resource_properties.MyProps == raw_event["ResourceProperties"].get("MyProps") -@event_parser(model=MyCustomResource) -def handle_create_custom_resource_extended_model(event: MyCustomResource, _: LambdaContext): - assert event.request_type == "Create" - assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" - assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" - assert ( - str(event.response_url) - == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" - ) - assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" - assert event.logical_resource_id == "xxxxxxxxx" - assert event.resource_type == "Custom::MyType" - assert event.resource_properties.MyProps == "ss" +def test_cloudformation_custom_resource_create_event(): + raw_event = load_event("cloudformationCustomResourceCreate.json") + model = CloudFormationCustomResourceCreateModel(**raw_event) -def test_create_trigger_event_custom_model(): - event_dict = load_event("cloudformationCustomResourceCreate.json") - handle_create_custom_resource_extended_model(event_dict, LambdaContext()) + assert model.request_type == raw_event["RequestType"] + assert model.request_id == raw_event["RequestId"] + assert model.service_token == raw_event["ServiceToken"] + assert str(model.response_url) == raw_event["ResponseURL"] + assert model.stack_id == raw_event["StackId"] + assert model.logical_resource_id == raw_event["LogicalResourceId"] + assert model.resource_type == raw_event["ResourceType"] + assert model.resource_properties == raw_event["ResourceProperties"] + + +def test_cloudformation_custom_resource_create_event_invalid(): + raw_event = load_event("cloudformationCustomResourceCreate.json") + raw_event["ResourceProperties"] = ["some_data"] + + with pytest.raises(ValidationError): + CloudFormationCustomResourceCreateModel(**raw_event) From 926be8fbdcc672aebaecd0568f9b47032930fac1 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:35:40 +0200 Subject: [PATCH 13/16] fix: resource_properties type to allow override --- .../parser/models/cloudformation_custom_resource.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py index 8be4ca41ca8..479ff53fb45 100644 --- a/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py +++ b/aws_lambda_powertools/utilities/parser/models/cloudformation_custom_resource.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Type, Union +from typing import Any, Dict, Union from pydantic import BaseModel, Field, HttpUrl @@ -13,7 +13,7 @@ class CloudFormationCustomResourceBaseModel(BaseModel): request_id: str = Field(..., alias="RequestId") logical_resource_id: str = Field(..., alias="LogicalResourceId") resource_type: str = Field(..., alias="ResourceType") - resource_properties: Union[Dict[str, Any], Type[BaseModel], None] = Field(None, alias="ResourceProperties") + resource_properties: Union[Dict[str, Any], BaseModel, None] = Field(None, alias="ResourceProperties") class CloudFormationCustomResourceCreateModel(CloudFormationCustomResourceBaseModel): @@ -26,4 +26,4 @@ class CloudFormationCustomResourceDeleteModel(CloudFormationCustomResourceBaseMo class CloudFormationCustomResourceUpdateModel(CloudFormationCustomResourceBaseModel): request_type: Literal["Update"] = Field(..., alias="RequestType") - old_resource_properties: Union[Dict[str, Any], Type[BaseModel], None] = Field(None, alias="OldResourceProperties") + old_resource_properties: Union[Dict[str, Any], BaseModel, None] = Field(None, alias="OldResourceProperties") From bd96a0467d87dad0fbd2bde9fe22a005d6e09088 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:41:06 +0200 Subject: [PATCH 14/16] chore: refactor delete test as unit test --- .../test_cloudformation_custom_resource.py | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/tests/unit/parser/test_cloudformation_custom_resource.py b/tests/unit/parser/test_cloudformation_custom_resource.py index b3e3b8021bb..f770dcf6c18 100644 --- a/tests/unit/parser/test_cloudformation_custom_resource.py +++ b/tests/unit/parser/test_cloudformation_custom_resource.py @@ -33,50 +33,23 @@ def handle_update_custom_resource(event: CloudFormationCustomResourceUpdateModel } -@event_parser(model=CloudFormationCustomResourceDeleteModel) -def handle_delete_custom_resource(event: CloudFormationCustomResourceDeleteModel, _: LambdaContext): - assert event.request_type == "Delete" - assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" - assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" - assert ( - str(event.response_url) - == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" - ) - assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" - assert event.logical_resource_id == "xxxxxxxxx" - assert event.resource_type == "Custom::MyType" - assert event.resource_properties == { - "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", - "MyProps": "ss", - } - - def test_update_trigger_event(): event_dict = load_event("cloudformationCustomResourceUpdate.json") handle_update_custom_resource(event_dict, LambdaContext()) -def test_delete_trigger_event(): - event_dict = load_event("cloudformationCustomResourceDelete.json") - handle_delete_custom_resource(event_dict, LambdaContext()) - - def test_validate_update_event_does_not_conform_with_model(): event = {"invalid": "event"} with pytest.raises(ValidationError): handle_update_custom_resource(event, LambdaContext()) -def test_validate_delete_event_does_not_conform_with_model(): - event = {"invalid": "event"} - with pytest.raises(ValidationError): - handle_delete_custom_resource(event, LambdaContext()) - - +# NOTE: Leftover, move to create model test class MyModel(BaseModel): MyProps: str +# NOTE: Leftover, update to test only custom model per se def test_cloudformation_custom_resource_create_event_custom_model(): class MyCustomResource(CloudFormationCustomResourceCreateModel): resource_properties: MyModel = Field(..., alias="ResourceProperties") @@ -114,3 +87,24 @@ def test_cloudformation_custom_resource_create_event_invalid(): with pytest.raises(ValidationError): CloudFormationCustomResourceCreateModel(**raw_event) + + +def test_cloudformation_custom_resource_delete_event(): + raw_event = load_event("cloudformationCustomResourceDelete.json") + model = CloudFormationCustomResourceDeleteModel(**raw_event) + + assert model.request_type == raw_event["RequestType"] + assert model.request_id == raw_event["RequestId"] + assert model.service_token == raw_event["ServiceToken"] + assert str(model.response_url) == raw_event["ResponseURL"] + assert model.stack_id == raw_event["StackId"] + assert model.logical_resource_id == raw_event["LogicalResourceId"] + assert model.resource_type == raw_event["ResourceType"] + assert model.resource_properties == raw_event["ResourceProperties"] + + +def test_cloudformation_custom_resource_delete_event_invalid(): + raw_event = load_event("cloudformationCustomResourceDelete.json") + raw_event["ResourceProperties"] = ["some_data"] + with pytest.raises(ValidationError): + CloudFormationCustomResourceDeleteModel(**raw_event) From 4f8b77469e00bff8956bf1c4707935629c5c55d4 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:45:59 +0200 Subject: [PATCH 15/16] chore: refactor update test as unit test --- .../test_cloudformation_custom_resource.py | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/tests/unit/parser/test_cloudformation_custom_resource.py b/tests/unit/parser/test_cloudformation_custom_resource.py index f770dcf6c18..44bc75e9a7b 100644 --- a/tests/unit/parser/test_cloudformation_custom_resource.py +++ b/tests/unit/parser/test_cloudformation_custom_resource.py @@ -1,49 +1,15 @@ import pytest from pydantic import BaseModel, Field -from aws_lambda_powertools.utilities.parser import ValidationError, event_parser +from aws_lambda_powertools.utilities.parser import ValidationError from aws_lambda_powertools.utilities.parser.models import ( CloudFormationCustomResourceCreateModel, CloudFormationCustomResourceDeleteModel, CloudFormationCustomResourceUpdateModel, ) -from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event -@event_parser(model=CloudFormationCustomResourceUpdateModel) -def handle_update_custom_resource(event: CloudFormationCustomResourceUpdateModel, _: LambdaContext): - assert event.request_type == "Update" - assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx" - assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe" - assert ( - str(event.response_url) - == "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b" - ) - assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21" - assert event.logical_resource_id == "xxxxxxxxx" - assert event.resource_type == "Custom::MyType" - assert event.resource_properties == { - "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx", - "MyProps": "new", - } - assert event.old_resource_properties == { - "ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx-xxxx-xxx", - "MyProps": "old", - } - - -def test_update_trigger_event(): - event_dict = load_event("cloudformationCustomResourceUpdate.json") - handle_update_custom_resource(event_dict, LambdaContext()) - - -def test_validate_update_event_does_not_conform_with_model(): - event = {"invalid": "event"} - with pytest.raises(ValidationError): - handle_update_custom_resource(event, LambdaContext()) - - # NOTE: Leftover, move to create model test class MyModel(BaseModel): MyProps: str @@ -108,3 +74,26 @@ def test_cloudformation_custom_resource_delete_event_invalid(): raw_event["ResourceProperties"] = ["some_data"] with pytest.raises(ValidationError): CloudFormationCustomResourceDeleteModel(**raw_event) + + +def test_cloudformation_custom_resource_update_event(): + raw_event = load_event("cloudformationCustomResourceUpdate.json") + model = CloudFormationCustomResourceUpdateModel(**raw_event) + + assert model.request_type == raw_event["RequestType"] + assert model.request_id == raw_event["RequestId"] + assert model.service_token == raw_event["ServiceToken"] + assert str(model.response_url) == raw_event["ResponseURL"] + assert model.stack_id == raw_event["StackId"] + assert model.logical_resource_id == raw_event["LogicalResourceId"] + assert model.resource_type == raw_event["ResourceType"] + assert model.resource_properties == raw_event["ResourceProperties"] + assert model.old_resource_properties == raw_event["OldResourceProperties"] + + +def test_cloudformation_custom_resource_update_event_invalid(): + raw_event = load_event("cloudformationCustomResourceUpdate.json") + raw_event["OldResourceProperties"] = ["some_data"] + + with pytest.raises(ValidationError): + CloudFormationCustomResourceUpdateModel(**raw_event) From 6be00f2ab6043f573451bfd70d5035ae0468bfe0 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 9 Jun 2023 17:48:41 +0200 Subject: [PATCH 16/16] chore: cleanup any leftovers, remove duplicate logic --- .../test_cloudformation_custom_resource.py | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/tests/unit/parser/test_cloudformation_custom_resource.py b/tests/unit/parser/test_cloudformation_custom_resource.py index 44bc75e9a7b..b5646c3f36a 100644 --- a/tests/unit/parser/test_cloudformation_custom_resource.py +++ b/tests/unit/parser/test_cloudformation_custom_resource.py @@ -10,18 +10,9 @@ from tests.functional.utils import load_event -# NOTE: Leftover, move to create model test -class MyModel(BaseModel): - MyProps: str - - -# NOTE: Leftover, update to test only custom model per se -def test_cloudformation_custom_resource_create_event_custom_model(): - class MyCustomResource(CloudFormationCustomResourceCreateModel): - resource_properties: MyModel = Field(..., alias="ResourceProperties") - +def test_cloudformation_custom_resource_create_event(): raw_event = load_event("cloudformationCustomResourceCreate.json") - model = MyCustomResource(**raw_event) + model = CloudFormationCustomResourceCreateModel(**raw_event) assert model.request_type == raw_event["RequestType"] assert model.request_id == raw_event["RequestId"] @@ -30,21 +21,20 @@ class MyCustomResource(CloudFormationCustomResourceCreateModel): assert model.stack_id == raw_event["StackId"] assert model.logical_resource_id == raw_event["LogicalResourceId"] assert model.resource_type == raw_event["ResourceType"] - assert model.resource_properties.MyProps == raw_event["ResourceProperties"].get("MyProps") + assert model.resource_properties == raw_event["ResourceProperties"] -def test_cloudformation_custom_resource_create_event(): +def test_cloudformation_custom_resource_create_event_custom_model(): + class MyModel(BaseModel): + MyProps: str + + class MyCustomResource(CloudFormationCustomResourceCreateModel): + resource_properties: MyModel = Field(..., alias="ResourceProperties") + raw_event = load_event("cloudformationCustomResourceCreate.json") - model = CloudFormationCustomResourceCreateModel(**raw_event) + model = MyCustomResource(**raw_event) - assert model.request_type == raw_event["RequestType"] - assert model.request_id == raw_event["RequestId"] - assert model.service_token == raw_event["ServiceToken"] - assert str(model.response_url) == raw_event["ResponseURL"] - assert model.stack_id == raw_event["StackId"] - assert model.logical_resource_id == raw_event["LogicalResourceId"] - assert model.resource_type == raw_event["ResourceType"] - assert model.resource_properties == raw_event["ResourceProperties"] + assert model.resource_properties.MyProps == raw_event["ResourceProperties"].get("MyProps") def test_cloudformation_custom_resource_create_event_invalid(): @@ -55,9 +45,9 @@ def test_cloudformation_custom_resource_create_event_invalid(): CloudFormationCustomResourceCreateModel(**raw_event) -def test_cloudformation_custom_resource_delete_event(): - raw_event = load_event("cloudformationCustomResourceDelete.json") - model = CloudFormationCustomResourceDeleteModel(**raw_event) +def test_cloudformation_custom_resource_update_event(): + raw_event = load_event("cloudformationCustomResourceUpdate.json") + model = CloudFormationCustomResourceUpdateModel(**raw_event) assert model.request_type == raw_event["RequestType"] assert model.request_id == raw_event["RequestId"] @@ -67,18 +57,20 @@ def test_cloudformation_custom_resource_delete_event(): assert model.logical_resource_id == raw_event["LogicalResourceId"] assert model.resource_type == raw_event["ResourceType"] assert model.resource_properties == raw_event["ResourceProperties"] + assert model.old_resource_properties == raw_event["OldResourceProperties"] -def test_cloudformation_custom_resource_delete_event_invalid(): - raw_event = load_event("cloudformationCustomResourceDelete.json") - raw_event["ResourceProperties"] = ["some_data"] +def test_cloudformation_custom_resource_update_event_invalid(): + raw_event = load_event("cloudformationCustomResourceUpdate.json") + raw_event["OldResourceProperties"] = ["some_data"] + with pytest.raises(ValidationError): - CloudFormationCustomResourceDeleteModel(**raw_event) + CloudFormationCustomResourceUpdateModel(**raw_event) -def test_cloudformation_custom_resource_update_event(): - raw_event = load_event("cloudformationCustomResourceUpdate.json") - model = CloudFormationCustomResourceUpdateModel(**raw_event) +def test_cloudformation_custom_resource_delete_event(): + raw_event = load_event("cloudformationCustomResourceDelete.json") + model = CloudFormationCustomResourceDeleteModel(**raw_event) assert model.request_type == raw_event["RequestType"] assert model.request_id == raw_event["RequestId"] @@ -88,12 +80,10 @@ def test_cloudformation_custom_resource_update_event(): assert model.logical_resource_id == raw_event["LogicalResourceId"] assert model.resource_type == raw_event["ResourceType"] assert model.resource_properties == raw_event["ResourceProperties"] - assert model.old_resource_properties == raw_event["OldResourceProperties"] -def test_cloudformation_custom_resource_update_event_invalid(): - raw_event = load_event("cloudformationCustomResourceUpdate.json") - raw_event["OldResourceProperties"] = ["some_data"] - +def test_cloudformation_custom_resource_delete_event_invalid(): + raw_event = load_event("cloudformationCustomResourceDelete.json") + raw_event["ResourceProperties"] = ["some_data"] with pytest.raises(ValidationError): - CloudFormationCustomResourceUpdateModel(**raw_event) + CloudFormationCustomResourceDeleteModel(**raw_event)