Skip to content

Commit 42a2acf

Browse files
authored
fix: Fix two places that could cause internal errors (#2930)
1 parent 2cfabd9 commit 42a2acf

File tree

11 files changed

+92
-74
lines changed

11 files changed

+92
-74
lines changed

samtranslator/model/__init__.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
from pydantic.error_wrappers import ValidationError
1010

1111
from samtranslator.intrinsics.resolver import IntrinsicsResolver
12-
from samtranslator.model.exceptions import ExpectedType, InvalidResourceException, InvalidResourcePropertyTypeException
12+
from samtranslator.model.exceptions import (
13+
ExpectedType,
14+
InvalidResourceException,
15+
InvalidResourcePropertyTypeException,
16+
)
1317
from samtranslator.model.tags.resource_tagging import get_tag_list
1418
from samtranslator.model.types import IS_DICT, IS_STR, Validator, any_type, is_type
1519
from samtranslator.plugins import LifeCycleEvents
@@ -121,7 +125,7 @@ class Resource(ABC):
121125

122126
def __init__(
123127
self,
124-
logical_id: str,
128+
logical_id: Optional[Any],
125129
relative_id: Optional[str] = None,
126130
depends_on: Optional[List[str]] = None,
127131
attributes: Optional[Dict[str, Any]] = None,
@@ -134,8 +138,7 @@ def __init__(
134138
:param depends_on Value of DependsOn resource attribute
135139
:param attributes Dictionary of resource attributes and their values
136140
"""
137-
self._validate_logical_id(logical_id) # type: ignore[no-untyped-call]
138-
self.logical_id = logical_id
141+
self.logical_id = self._validate_logical_id(logical_id)
139142
self.relative_id = relative_id
140143
self.depends_on = depends_on
141144

@@ -214,8 +217,8 @@ def from_dict(cls, logical_id: str, resource_dict: Dict[str, Any], relative_id:
214217
resource.validate_properties()
215218
return resource
216219

217-
@classmethod
218-
def _validate_logical_id(cls, logical_id): # type: ignore[no-untyped-def]
220+
@staticmethod
221+
def _validate_logical_id(logical_id: Optional[Any]) -> str:
219222
"""Validates that the provided logical id is an alphanumeric string.
220223
221224
:param str logical_id: the logical id to validate
@@ -224,9 +227,12 @@ def _validate_logical_id(cls, logical_id): # type: ignore[no-untyped-def]
224227
:raises TypeError: if the logical id is invalid
225228
"""
226229
pattern = re.compile(r"^[A-Za-z0-9]+$")
227-
if logical_id is not None and pattern.match(logical_id):
228-
return True
229-
raise InvalidResourceException(logical_id, "Logical ids must be alphanumeric.")
230+
if isinstance(logical_id, str) and pattern.match(logical_id):
231+
return logical_id
232+
# TODO: Doing validation in this class is kind of off,
233+
# we need to surface this validation to where the template is loaded
234+
# or the logical IDs are generated.
235+
raise InvalidResourceException(str(logical_id), "Logical ids must be alphanumeric.")
230236

231237
@classmethod
232238
def _validate_resource_dict(cls, logical_id, resource_dict): # type: ignore[no-untyped-def]

samtranslator/model/eventsources/push.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -634,47 +634,49 @@ class Api(PushEventSource):
634634
RequestModel: Optional[Dict[str, Any]]
635635
RequestParameters: Optional[List[Any]]
636636

637-
def resources_to_link(self, resources): # type: ignore[no-untyped-def]
637+
def resources_to_link(self, resources: Dict[str, Any]) -> Dict[str, Any]:
638638
"""
639639
If this API Event Source refers to an explicit API resource, resolve the reference and grab
640640
necessary data from the explicit API
641641
"""
642+
return self.resources_to_link_for_rest_api(resources, self.relative_id, self.RestApiId)
642643

644+
@staticmethod
645+
def resources_to_link_for_rest_api(
646+
resources: Dict[str, Any], relative_id: str, raw_rest_api_id: Optional[Any]
647+
) -> Dict[str, Any]:
643648
# If RestApiId is a resource in the same template, then we try find the StageName by following the reference
644649
# Otherwise we default to a wildcard. This stage name is solely used to construct the permission to
645650
# allow this stage to invoke the Lambda function. If we are unable to resolve the stage name, we will
646651
# simply permit all stages to invoke this Lambda function
647652
# This hack is necessary because customers could use !ImportValue, !Ref or other intrinsic functions which
648653
# can be sometimes impossible to resolve (ie. when it has cross-stack references)
649-
permitted_stage = "*"
650654
stage_suffix = "AllStages"
651-
explicit_api = None
652-
rest_api_id = self.get_rest_api_id_string(self.RestApiId)
655+
explicit_api_resource_properties = None
656+
rest_api_id = Api.get_rest_api_id_string(raw_rest_api_id)
653657
if isinstance(rest_api_id, str):
654-
if (
655-
rest_api_id in resources
656-
and "Properties" in resources[rest_api_id]
657-
and "StageName" in resources[rest_api_id]["Properties"]
658-
):
659-
explicit_api = resources[rest_api_id]["Properties"]
660-
permitted_stage = explicit_api["StageName"]
661-
662-
# Stage could be a intrinsic, in which case leave the suffix to default value
663-
if isinstance(permitted_stage, str):
664-
if not permitted_stage:
665-
raise InvalidResourceException(rest_api_id, "StageName cannot be empty.")
666-
stage_suffix = permitted_stage
667-
else:
668-
stage_suffix = "Stage" # type: ignore[unreachable]
669-
658+
rest_api_resource = sam_expect(
659+
resources.get(rest_api_id), relative_id, "RestApiId", is_sam_event=True
660+
).to_be_a_map("RestApiId property of Api event must reference a valid resource in the same template.")
661+
662+
explicit_api_resource_properties = sam_expect(
663+
rest_api_resource.get("Properties", {}), rest_api_id, "Properties", is_resource_attribute=True
664+
).to_be_a_map()
665+
permitted_stage = explicit_api_resource_properties.get("StageName")
666+
667+
# Stage could be an intrinsic, in which case leave the suffix to default value
668+
if isinstance(permitted_stage, str):
669+
if not permitted_stage:
670+
raise InvalidResourceException(rest_api_id, "StageName cannot be empty.")
671+
stage_suffix = permitted_stage
670672
else:
671-
# RestApiId is a string, not an intrinsic, but we did not find a valid API resource for this ID
672-
raise InvalidEventException(
673-
self.relative_id,
674-
"RestApiId property of Api event must reference a valid resource in the same template.",
675-
)
673+
stage_suffix = "Stage"
676674

677-
return {"explicit_api": explicit_api, "api_id": rest_api_id, "explicit_api_stage": {"suffix": stage_suffix}}
675+
return {
676+
"explicit_api": explicit_api_resource_properties,
677+
"api_id": rest_api_id,
678+
"explicit_api_stage": {"suffix": stage_suffix},
679+
}
678680

679681
@cw_timer(prefix=FUNCTION_EVETSOURCE_METRIC_PREFIX)
680682
def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]

samtranslator/model/stepfunctions/events.py

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -299,42 +299,12 @@ class Api(EventSource):
299299
Auth: Optional[Dict[str, Any]]
300300
UnescapeMappingTemplate: Optional[bool]
301301

302-
def resources_to_link(self, resources): # type: ignore[no-untyped-def]
302+
def resources_to_link(self, resources: Dict[str, Any]) -> Dict[str, Any]:
303303
"""
304304
If this API Event Source refers to an explicit API resource, resolve the reference and grab
305305
necessary data from the explicit API
306306
"""
307-
308-
# If RestApiId is a resource in the same template, then we try find the StageName by following the reference
309-
# Otherwise we default to a wildcard. This stage name is solely used to construct the permission to
310-
# allow this stage to invoke the State Machine. If we are unable to resolve the stage name, we will
311-
# simply permit all stages to invoke this State Machine
312-
# This hack is necessary because customers could use !ImportValue, !Ref or other intrinsic functions which
313-
# can be sometimes impossible to resolve (ie. when it has cross-stack references)
314-
permitted_stage = "*"
315-
stage_suffix = "AllStages"
316-
explicit_api = None
317-
rest_api_id = PushApi.get_rest_api_id_string(self.RestApiId)
318-
if isinstance(rest_api_id, str):
319-
if (
320-
rest_api_id in resources
321-
and "Properties" in resources[rest_api_id]
322-
and "StageName" in resources[rest_api_id]["Properties"]
323-
):
324-
explicit_api = resources[rest_api_id]["Properties"]
325-
permitted_stage = explicit_api["StageName"]
326-
327-
# Stage could be a intrinsic, in which case leave the suffix to default value
328-
stage_suffix = permitted_stage if isinstance(permitted_stage, str) else "Stage"
329-
330-
else:
331-
# RestApiId is a string, not an intrinsic, but we did not find a valid API resource for this ID
332-
raise InvalidEventException(
333-
self.relative_id,
334-
"RestApiId property of Api event must reference a valid resource in the same template.",
335-
)
336-
337-
return {"explicit_api": explicit_api, "api_id": rest_api_id, "explicit_api_stage": {"suffix": stage_suffix}}
307+
return PushApi.resources_to_link_for_rest_api(resources, self.relative_id, self.RestApiId)
338308

339309
@cw_timer(prefix=SFN_EVETSOURCE_METRIC_PREFIX)
340310
def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]

samtranslator/plugins/api/implicit_api_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # type: ig
207207
and template.get(api_id).type != self.SERVERLESS_API_RESOURCE_TYPE
208208
)
209209

210-
# RestApiId is not pointing to a valid API resource
210+
# RestApiId is not pointing to a valid API resource
211211
if isinstance(api_id, dict) or is_referencing_http_from_api_event:
212212
raise InvalidEventException(
213213
event_id,

tests/model/stepfunctions/test_api_event.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def test_resources_to_link_with_explicit_api(self):
7575

7676
def test_resources_to_link_with_undefined_explicit_api(self):
7777
resources = {}
78+
self.api_event_source.relative_id = "event_id"
7879
self.api_event_source.RestApiId = {"Ref": "MyExplicitApi"}
7980
with self.assertRaises(InvalidEventException):
8081
self.api_event_source.resources_to_link(resources)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
3+
Resources:
4+
MyFunction:
5+
Type: AWS::Serverless::Function
6+
Properties:
7+
Runtime: python3.9
8+
Events:
9+
Event1:
10+
Type: Api
11+
Properties:
12+
Path: /channel/limit-validate
13+
RestApiId: RestApi
14+
Method: OPTIONS
15+
16+
RestApi:
17+
Type: AWS::Serverless::Api
18+
Properties: 123
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
3+
Resources:
4+
MyFunction:
5+
Type: AWS::Serverless::Function
6+
Properties:
7+
Runtime: python3.9
8+
Events:
9+
Event1:
10+
Type: Api
11+
Properties:
12+
Path: /channel/limit-validate
13+
RestApiId: RestApi
14+
Method: OPTIONS

tests/translator/input/error_invalid_logical_id.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ Resources:
55
Runtime: python2.7
66
Handler: hello.handler
77
CodeUri: s3://bucket/code.zip
8+
0:
9+
Type: AWS::Serverless::Function
10+
Properties:
11+
Runtime: python2.7
12+
Handler: hello.handler
13+
CodeUri: s3://bucket/code.zip
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [RestApi] is invalid. Attribute 'Properties' should be a map."
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyFunction] is invalid. Event with id [Event1] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template."
3+
}

0 commit comments

Comments
 (0)