Skip to content

Commit b88c545

Browse files
author
Ran Isenberg
committed
feat(parser): Parser support for custom resource
1 parent cb55707 commit b88c545

File tree

7 files changed

+213
-0
lines changed

7 files changed

+213
-0
lines changed

aws_lambda_powertools/utilities/parser/models/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
CloudWatchLogsLogEvent,
2121
CloudWatchLogsModel,
2222
)
23+
from .custom_resource import (
24+
CustomResourceBaseModel,
25+
CustomResourceCreateModel,
26+
CustomResourceDeleteModel,
27+
CustomResourceUpdateModel,
28+
)
2329
from .dynamodb import (
2430
DynamoDBStreamChangedRecordModel,
2531
DynamoDBStreamModel,
@@ -147,4 +153,8 @@
147153
"KafkaBaseEventModel",
148154
"KinesisFirehoseSqsModel",
149155
"KinesisFirehoseSqsRecord",
156+
"CustomResourceUpdateModel",
157+
"CustomResourceDeleteModel",
158+
"CustomResourceCreateModel",
159+
"CustomResourceBaseModel",
150160
]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Any, Dict, Literal, Type, Union
2+
3+
from pydantic import BaseModel, Field, HttpUrl
4+
5+
6+
class CustomResourceBaseModel(BaseModel):
7+
request_type: str = Field(..., alias="RequestType")
8+
service_token: str = Field(..., alias="ServiceToken")
9+
response_url: HttpUrl = Field(..., alias="ResponseURL")
10+
stack_id: str = Field(..., alias="StackId")
11+
request_id: str = Field(..., alias="RequestId")
12+
logical_resource_id: str = Field(..., alias="LogicalResourceId")
13+
resource_type: str = Field(..., alias="ResourceType")
14+
resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="ResourceProperties")
15+
16+
17+
class CustomResourceCreateModel(CustomResourceBaseModel):
18+
request_type: Literal["Create"] = Field(..., alias="RequestType")
19+
20+
21+
class CustomResourceDeleteModel(CustomResourceBaseModel):
22+
request_type: Literal["Delete"] = Field(..., alias="RequestType")
23+
24+
25+
class CustomResourceUpdateModel(CustomResourceBaseModel):
26+
request_type: Literal["Update"] = Field(..., alias="RequestType")
27+
old_resource_properties: Union[Dict[str, Any], Type[BaseModel]] = Field(..., alias="OldResourceProperties")

docs/utilities/parser.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ Parser comes with the following built-in models:
177177
| **SesModel** | Lambda Event Source payload for Amazon Simple Email Service |
178178
| **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service |
179179
| **SqsModel** | Lambda Event Source payload for Amazon SQS |
180+
| **CustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation 'create' custom resource |
181+
| **CustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation 'update' custom resource |
182+
| **CustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation 'delete' custom resource |
180183

181184
#### Extending built-in models
182185

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"RequestType": "Create",
3+
"ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe",
4+
"ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b",
5+
"StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21",
6+
"RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx",
7+
"LogicalResourceId": "xxxxxxxxx",
8+
"ResourceType": "Custom::MyType",
9+
"ResourceProperties": {
10+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
11+
"MyProps": "ss"
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"RequestType": "Delete",
3+
"ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe",
4+
"ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b",
5+
"StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21",
6+
"RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx",
7+
"LogicalResourceId": "xxxxxxxxx",
8+
"ResourceType": "Custom::MyType",
9+
"ResourceProperties": {
10+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
11+
"MyProps": "ss"
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"RequestType": "Update",
3+
"ServiceToken": "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe",
4+
"ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b",
5+
"StackId": "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21",
6+
"RequestId": "xxxxx-d2a0-4dfb-ab1f-xxxxxx",
7+
"LogicalResourceId": "xxxxxxxxx",
8+
"ResourceType": "Custom::MyType",
9+
"ResourceProperties": {
10+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
11+
"MyProps": "new"
12+
},
13+
"OldResourceProperties": {
14+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx-xxxx-xxx",
15+
"MyProps": "old"
16+
}
17+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import pytest
2+
from pydantic import BaseModel, Field
3+
4+
from aws_lambda_powertools.utilities.parser import ValidationError, event_parser
5+
from aws_lambda_powertools.utilities.parser.models import (
6+
CustomResourceCreateModel,
7+
CustomResourceDeleteModel,
8+
CustomResourceUpdateModel,
9+
)
10+
from aws_lambda_powertools.utilities.typing import LambdaContext
11+
from tests.functional.utils import load_event
12+
13+
14+
@event_parser(model=CustomResourceCreateModel)
15+
def handle_create_custom_resource(event: CustomResourceCreateModel, _: LambdaContext):
16+
assert event.request_type == "Create"
17+
assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx"
18+
assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe"
19+
assert (
20+
str(event.response_url)
21+
== "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b"
22+
)
23+
assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21"
24+
assert event.logical_resource_id == "xxxxxxxxx"
25+
assert event.resource_type == "Custom::MyType"
26+
assert event.resource_properties == {
27+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
28+
"MyProps": "ss",
29+
}
30+
31+
32+
@event_parser(model=CustomResourceUpdateModel)
33+
def handle_update_custom_resource(event: CustomResourceUpdateModel, _: LambdaContext):
34+
assert event.request_type == "Update"
35+
assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx"
36+
assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe"
37+
assert (
38+
str(event.response_url)
39+
== "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b"
40+
)
41+
assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21"
42+
assert event.logical_resource_id == "xxxxxxxxx"
43+
assert event.resource_type == "Custom::MyType"
44+
assert event.resource_properties == {
45+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
46+
"MyProps": "new",
47+
}
48+
assert event.old_resource_properties == {
49+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx-xxxx-xxx",
50+
"MyProps": "old",
51+
}
52+
53+
54+
@event_parser(model=CustomResourceDeleteModel)
55+
def handle_delete_custom_resource(event: CustomResourceDeleteModel, _: LambdaContext):
56+
assert event.request_type == "Delete"
57+
assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx"
58+
assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe"
59+
assert (
60+
str(event.response_url)
61+
== "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b"
62+
)
63+
assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21"
64+
assert event.logical_resource_id == "xxxxxxxxx"
65+
assert event.resource_type == "Custom::MyType"
66+
assert event.resource_properties == {
67+
"ServiceToken": "arn:aws:lambda:us-east-1:xxxxx:function:xxxxx",
68+
"MyProps": "ss",
69+
}
70+
71+
72+
def test_create_trigger_event():
73+
event_dict = load_event("customResourceCreate.json")
74+
handle_create_custom_resource(event_dict, LambdaContext())
75+
76+
77+
def test_update_trigger_event():
78+
event_dict = load_event("customResourceUpdate.json")
79+
handle_update_custom_resource(event_dict, LambdaContext())
80+
81+
82+
def test_delete_trigger_event():
83+
event_dict = load_event("customResourceDelete.json")
84+
handle_delete_custom_resource(event_dict, LambdaContext())
85+
86+
87+
def test_validate_create_event_does_not_conform_with_model():
88+
event = {"invalid": "event"}
89+
with pytest.raises(ValidationError):
90+
handle_create_custom_resource(event, LambdaContext())
91+
92+
93+
def test_validate_update_event_does_not_conform_with_model():
94+
event = {"invalid": "event"}
95+
with pytest.raises(ValidationError):
96+
handle_update_custom_resource(event, LambdaContext())
97+
98+
99+
def test_validate_delete_event_does_not_conform_with_model():
100+
event = {"invalid": "event"}
101+
with pytest.raises(ValidationError):
102+
handle_delete_custom_resource(event, LambdaContext())
103+
104+
105+
class MyModel(BaseModel):
106+
MyProps: str
107+
108+
109+
class MyCustomResource(CustomResourceCreateModel):
110+
resource_properties: MyModel = Field(..., alias="ResourceProperties")
111+
112+
113+
@event_parser(model=MyCustomResource)
114+
def handle_create_custom_resource_extended_model(event: MyCustomResource, _: LambdaContext):
115+
assert event.request_type == "Create"
116+
assert event.request_id == "xxxxx-d2a0-4dfb-ab1f-xxxxxx"
117+
assert event.service_token == "arn:aws:lambda:us-east-1:xxx:function:xxxx-CrbuiltinfunctionidProvi-2vKAalSppmKe"
118+
assert (
119+
str(event.response_url)
120+
== "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/7F%7Cb1f50fdfc25f3b"
121+
)
122+
assert event.stack_id == "arn:aws:cloudformation:us-east-1:xxxx:stack/xxxx/271845b0-f2e8-11ed-90ac-0eeb25b8ae21"
123+
assert event.logical_resource_id == "xxxxxxxxx"
124+
assert event.resource_type == "Custom::MyType"
125+
assert event.resource_properties.MyProps == "ss"
126+
127+
128+
def test_create_trigger_event_custom_model():
129+
event_dict = load_event("customResourceCreate.json")
130+
handle_create_custom_resource_extended_model(event_dict, LambdaContext())

0 commit comments

Comments
 (0)