Skip to content

Commit 2ceb630

Browse files
feat(event_handler): add support to VPC Lattice payload v2 (aws-powertools#3153)
Co-authored-by: Leandro Damascena <[email protected]>
1 parent 8dc239b commit 2ceb630

21 files changed

+651
-33
lines changed

aws_lambda_powertools/event_handler/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from aws_lambda_powertools.event_handler.lambda_function_url import (
1515
LambdaFunctionUrlResolver,
1616
)
17-
from aws_lambda_powertools.event_handler.vpc_lattice import VPCLatticeResolver
17+
from aws_lambda_powertools.event_handler.vpc_lattice import VPCLatticeResolver, VPCLatticeV2Resolver
1818

1919
__all__ = [
2020
"AppSyncResolver",
@@ -26,4 +26,5 @@
2626
"LambdaFunctionUrlResolver",
2727
"Response",
2828
"VPCLatticeResolver",
29+
"VPCLatticeV2Resolver",
2930
]

aws_lambda_powertools/event_handler/api_gateway.py

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
APIGatewayProxyEventV2,
2323
LambdaFunctionUrlEvent,
2424
VPCLatticeEvent,
25+
VPCLatticeEventV2,
2526
)
2627
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
2728
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -43,6 +44,7 @@ class ProxyEventType(Enum):
4344
APIGatewayProxyEventV2 = "APIGatewayProxyEventV2"
4445
ALBEvent = "ALBEvent"
4546
VPCLatticeEvent = "VPCLatticeEvent"
47+
VPCLatticeEventV2 = "VPCLatticeEventV2"
4648
LambdaFunctionUrlEvent = "LambdaFunctionUrlEvent"
4749

4850

@@ -999,6 +1001,9 @@ def _to_proxy_event(self, event: Dict) -> BaseProxyEvent:
9991001
if self._proxy_type == ProxyEventType.VPCLatticeEvent:
10001002
logger.debug("Converting event to VPC Lattice contract")
10011003
return VPCLatticeEvent(event)
1004+
if self._proxy_type == ProxyEventType.VPCLatticeEventV2:
1005+
logger.debug("Converting event to VPC LatticeV2 contract")
1006+
return VPCLatticeEventV2(event)
10021007
logger.debug("Converting event to ALB contract")
10031008
return ALBEvent(event)
10041009

aws_lambda_powertools/event_handler/vpc_lattice.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ApiGatewayResolver,
66
ProxyEventType,
77
)
8-
from aws_lambda_powertools.utilities.data_classes import VPCLatticeEvent
8+
from aws_lambda_powertools.utilities.data_classes import VPCLatticeEvent, VPCLatticeEventV2
99

1010

1111
class VPCLatticeResolver(ApiGatewayResolver):
@@ -51,3 +51,48 @@ def __init__(
5151
):
5252
"""Amazon VPC Lattice resolver"""
5353
super().__init__(ProxyEventType.VPCLatticeEvent, cors, debug, serializer, strip_prefixes)
54+
55+
56+
class VPCLatticeV2Resolver(ApiGatewayResolver):
57+
"""VPC Lattice resolver
58+
59+
Documentation:
60+
- https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html
61+
- https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html#vpc-lattice-receiving-events
62+
63+
Examples
64+
--------
65+
Simple example integrating with Tracer
66+
67+
```python
68+
from aws_lambda_powertools import Tracer
69+
from aws_lambda_powertools.event_handler import VPCLatticeV2Resolver
70+
71+
tracer = Tracer()
72+
app = VPCLatticeV2Resolver()
73+
74+
@app.get("/get-call")
75+
def simple_get():
76+
return {"message": "Foo"}
77+
78+
@app.post("/post-call")
79+
def simple_post():
80+
post_data: dict = app.current_event.json_body
81+
return {"message": post_data}
82+
83+
@tracer.capture_lambda_handler
84+
def lambda_handler(event, context):
85+
return app.resolve(event, context)
86+
"""
87+
88+
current_event: VPCLatticeEventV2
89+
90+
def __init__(
91+
self,
92+
cors: Optional[CORSConfig] = None,
93+
debug: Optional[bool] = None,
94+
serializer: Optional[Callable[[Dict], str]] = None,
95+
strip_prefixes: Optional[List[Union[str, Pattern]]] = None,
96+
):
97+
"""Amazon VPC Lattice resolver"""
98+
super().__init__(ProxyEventType.VPCLatticeEventV2, cors, debug, serializer, strip_prefixes)

aws_lambda_powertools/utilities/data_classes/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .ses_event import SESEvent
2828
from .sns_event import SNSEvent
2929
from .sqs_event import SQSEvent
30-
from .vpc_lattice import VPCLatticeEvent
30+
from .vpc_lattice import VPCLatticeEvent, VPCLatticeEventV2
3131

3232
__all__ = [
3333
"APIGatewayProxyEvent",
@@ -56,4 +56,5 @@
5656
"event_source",
5757
"AWSConfigRuleEvent",
5858
"VPCLatticeEvent",
59+
"VPCLatticeEventV2",
5960
]

aws_lambda_powertools/utilities/data_classes/vpc_lattice.py

+136-25
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
BaseHeadersSerializer,
55
HttpApiHeadersSerializer,
66
)
7-
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
7+
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent, DictWrapper
88
from aws_lambda_powertools.utilities.data_classes.shared_functions import (
99
base64_decode,
1010
get_header_value,
1111
get_query_string_value,
1212
)
1313

1414

15-
class VPCLatticeEvent(BaseProxyEvent):
15+
class VPCLatticeEventBase(BaseProxyEvent):
1616
@property
1717
def body(self) -> str:
1818
"""The VPC Lattice body."""
@@ -30,11 +30,6 @@ def headers(self) -> Dict[str, str]:
3030
"""The VPC Lattice event headers."""
3131
return self["headers"]
3232

33-
@property
34-
def is_base64_encoded(self) -> bool:
35-
"""A boolean flag to indicate if the applicable request payload is Base64-encode"""
36-
return self["is_base64_encoded"]
37-
3833
@property
3934
def decoded_body(self) -> str:
4035
"""Dynamically base64 decode body as a str"""
@@ -48,24 +43,6 @@ def method(self) -> str:
4843
"""The VPC Lattice method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
4944
return self["method"]
5045

51-
@property
52-
def query_string_parameters(self) -> Dict[str, str]:
53-
"""The request query string parameters."""
54-
return self["query_string_parameters"]
55-
56-
@property
57-
def raw_path(self) -> str:
58-
"""The raw VPC Lattice request path."""
59-
return self["raw_path"]
60-
61-
# VPCLattice event has no path field
62-
# Added here for consistency with the BaseProxyEvent class
63-
@property
64-
def path(self) -> str:
65-
return self["raw_path"]
66-
67-
# VPCLattice event has no http_method field
68-
# Added here for consistency with the BaseProxyEvent class
6946
@property
7047
def http_method(self) -> str:
7148
"""The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
@@ -140,3 +117,137 @@ def get_header_value(
140117
def header_serializer(self) -> BaseHeadersSerializer:
141118
# When using the VPC Lattice integration, we have multiple HTTP Headers.
142119
return HttpApiHeadersSerializer()
120+
121+
122+
class VPCLatticeEvent(VPCLatticeEventBase):
123+
@property
124+
def raw_path(self) -> str:
125+
"""The raw VPC Lattice request path."""
126+
return self["raw_path"]
127+
128+
@property
129+
def is_base64_encoded(self) -> bool:
130+
"""A boolean flag to indicate if the applicable request payload is Base64-encode"""
131+
return self["is_base64_encoded"]
132+
133+
# VPCLattice event has no path field
134+
# Added here for consistency with the BaseProxyEvent class
135+
@property
136+
def path(self) -> str:
137+
return self["raw_path"]
138+
139+
@property
140+
def query_string_parameters(self) -> Dict[str, str]:
141+
"""The request query string parameters."""
142+
return self["query_string_parameters"]
143+
144+
145+
class vpcLatticeEventV2Identity(DictWrapper):
146+
@property
147+
def source_vpc_arn(self) -> Optional[str]:
148+
"""The VPC Lattice v2 Event requestContext Identity sourceVpcArn"""
149+
return self.get("sourceVpcArn")
150+
151+
@property
152+
def get_type(self) -> Optional[str]:
153+
"""The VPC Lattice v2 Event requestContext Identity type"""
154+
return self.get("type")
155+
156+
@property
157+
def principal(self) -> Optional[str]:
158+
"""The VPC Lattice v2 Event requestContext principal"""
159+
return self.get("principal")
160+
161+
@property
162+
def principal_org_id(self) -> Optional[str]:
163+
"""The VPC Lattice v2 Event requestContext principalOrgID"""
164+
return self.get("principalOrgID")
165+
166+
@property
167+
def session_name(self) -> Optional[str]:
168+
"""The VPC Lattice v2 Event requestContext sessionName"""
169+
return self.get("sessionName")
170+
171+
@property
172+
def x509_subject_cn(self) -> Optional[str]:
173+
"""The VPC Lattice v2 Event requestContext X509SubjectCn"""
174+
return self.get("X509SubjectCn")
175+
176+
@property
177+
def x509_issuer_ou(self) -> Optional[str]:
178+
"""The VPC Lattice v2 Event requestContext X509IssuerOu"""
179+
return self.get("X509IssuerOu")
180+
181+
@property
182+
def x509_san_dns(self) -> Optional[str]:
183+
"""The VPC Lattice v2 Event requestContext X509SanDns"""
184+
return self.get("x509SanDns")
185+
186+
@property
187+
def x509_san_uri(self) -> Optional[str]:
188+
"""The VPC Lattice v2 Event requestContext X509SanUri"""
189+
return self.get("X509SanUri")
190+
191+
@property
192+
def x509_san_name_cn(self) -> Optional[str]:
193+
"""The VPC Lattice v2 Event requestContext X509SanNameCn"""
194+
return self.get("X509SanNameCn")
195+
196+
197+
class vpcLatticeEventV2RequestContext(DictWrapper):
198+
@property
199+
def service_network_arn(self) -> str:
200+
"""The VPC Lattice v2 Event requestContext serviceNetworkArn"""
201+
return self["serviceNetworkArn"]
202+
203+
@property
204+
def service_arn(self) -> str:
205+
"""The VPC Lattice v2 Event requestContext serviceArn"""
206+
return self["serviceArn"]
207+
208+
@property
209+
def target_group_arn(self) -> str:
210+
"""The VPC Lattice v2 Event requestContext targetGroupArn"""
211+
return self["targetGroupArn"]
212+
213+
@property
214+
def identity(self) -> vpcLatticeEventV2Identity:
215+
"""The VPC Lattice v2 Event requestContext identity"""
216+
return vpcLatticeEventV2Identity(self["identity"])
217+
218+
@property
219+
def region(self) -> str:
220+
"""The VPC Lattice v2 Event requestContext serviceNetworkArn"""
221+
return self["region"]
222+
223+
@property
224+
def time_epoch(self) -> float:
225+
"""The VPC Lattice v2 Event requestContext timeEpoch"""
226+
return self["timeEpoch"]
227+
228+
229+
class VPCLatticeEventV2(VPCLatticeEventBase):
230+
@property
231+
def version(self) -> str:
232+
"""The VPC Lattice v2 Event version"""
233+
return self["version"]
234+
235+
@property
236+
def is_base64_encoded(self) -> Optional[bool]:
237+
"""A boolean flag to indicate if the applicable request payload is Base64-encode"""
238+
return self.get("isBase64Encoded")
239+
240+
@property
241+
def path(self) -> str:
242+
"""The VPC Lattice v2 Event path"""
243+
return self["path"]
244+
245+
@property
246+
def request_context(self) -> vpcLatticeEventV2RequestContext:
247+
"""he VPC Lattice v2 Event request context."""
248+
return vpcLatticeEventV2RequestContext(self["requestContext"])
249+
250+
@property
251+
def query_string_parameters(self) -> Optional[Dict[str, str]]:
252+
"""The request query string parameters."""
253+
return self.get("queryStringParameters")

aws_lambda_powertools/utilities/parser/envelopes/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .sns import SnsEnvelope, SnsSqsEnvelope
1212
from .sqs import SqsEnvelope
1313
from .vpc_lattice import VpcLatticeEnvelope
14+
from .vpc_latticev2 import VpcLatticeV2Envelope
1415

1516
__all__ = [
1617
"ApiGatewayEnvelope",
@@ -27,4 +28,5 @@
2728
"KafkaEnvelope",
2829
"BaseEnvelope",
2930
"VpcLatticeEnvelope",
31+
"VpcLatticeV2Envelope",
3032
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import logging
2+
from typing import Any, Dict, Optional, Type, Union
3+
4+
from ..models import VpcLatticeV2Model
5+
from ..types import Model
6+
from .base import BaseEnvelope
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class VpcLatticeV2Envelope(BaseEnvelope):
12+
"""Amazon VPC Lattice envelope to extract data within body key"""
13+
14+
def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) -> Optional[Model]:
15+
"""Parses data found with model provided
16+
17+
Parameters
18+
----------
19+
data : Dict
20+
Lambda event to be parsed
21+
model : Type[Model]
22+
Data model provided to parse after extracting data using envelope
23+
24+
Returns
25+
-------
26+
Optional[Model]
27+
Parsed detail payload with model provided
28+
"""
29+
logger.debug(f"Parsing incoming data with VPC Lattice V2 model {VpcLatticeV2Model}")
30+
parsed_envelope: VpcLatticeV2Model = VpcLatticeV2Model.parse_obj(data)
31+
logger.debug(f"Parsing event payload in `detail` with {model}")
32+
return self._parse(data=parsed_envelope.body, model=model)

aws_lambda_powertools/utilities/parser/models/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
from .sns import SnsModel, SnsNotificationModel, SnsRecordModel
9090
from .sqs import SqsAttributesModel, SqsModel, SqsMsgAttributeModel, SqsRecordModel
9191
from .vpc_lattice import VpcLatticeModel
92+
from .vpc_latticev2 import VpcLatticeV2Model
9293

9394
__all__ = [
9495
"APIGatewayProxyEventV2Model",
@@ -163,4 +164,5 @@
163164
"CloudFormationCustomResourceCreateModel",
164165
"CloudFormationCustomResourceBaseModel",
165166
"VpcLatticeModel",
167+
"VpcLatticeV2Model",
166168
]

0 commit comments

Comments
 (0)