Skip to content

Commit d3b1823

Browse files
author
Connor Robertson
authored
Merge pull request #3390 from aws/release-v1.79.0
Release 1.79.0 (to main)
2 parents 3966719 + 30a75c7 commit d3b1823

22 files changed

+1554
-129
lines changed

samtranslator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.78.0"
1+
__version__ = "1.79.0"

samtranslator/internal/schema_source/aws_serverless_statemachine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ScheduleEventProperties(BaseModel):
4949
Schedule: Optional[PassThroughProp] = scheduleeventproperties("Schedule")
5050
State: Optional[PassThroughProp] = scheduleeventproperties("State")
5151
Target: Optional[ScheduleTarget] = scheduleeventproperties("Target")
52+
RoleArn: Optional[PassThroughProp] # TODO: add doc
5253

5354

5455
class ScheduleEvent(BaseModel):

samtranslator/model/eventsources/push.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@
1212
from samtranslator.model.eventsources import FUNCTION_EVETSOURCE_METRIC_PREFIX
1313
from samtranslator.model.eventsources.pull import SQS
1414
from samtranslator.model.exceptions import InvalidDocumentException, InvalidEventException, InvalidResourceException
15-
from samtranslator.model.intrinsics import fnGetAtt, fnSub, is_intrinsic, make_conditional, make_shorthand, ref
15+
from samtranslator.model.intrinsics import (
16+
fnGetAtt,
17+
fnSub,
18+
get_logical_id_from_intrinsic,
19+
is_intrinsic,
20+
make_conditional,
21+
make_shorthand,
22+
ref,
23+
)
1624
from samtranslator.model.iot import IotTopicRule
1725
from samtranslator.model.lambda_ import LambdaPermission
1826
from samtranslator.model.s3 import S3Bucket
@@ -517,6 +525,8 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
517525
if not function:
518526
raise TypeError("Missing required keyword argument: function")
519527

528+
intrinsics_resolver: IntrinsicsResolver = kwargs["intrinsics_resolver"]
529+
520530
# SNS -> Lambda
521531
if not self.SqsSubscription:
522532
subscription = self._inject_subscription(
@@ -534,7 +544,11 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
534544
# SNS -> SQS(Create New) -> Lambda
535545
if isinstance(self.SqsSubscription, bool):
536546
resources = [] # type: ignore[var-annotated]
537-
queue = self._inject_sqs_queue(function) # type: ignore[no-untyped-call]
547+
548+
fifo_topic = self._check_fifo_topic(
549+
get_logical_id_from_intrinsic(self.Topic), kwargs.get("original_template"), intrinsics_resolver
550+
)
551+
queue = self._inject_sqs_queue(function, fifo_topic) # type: ignore[no-untyped-call]
538552
queue_arn = queue.get_runtime_attr("arn")
539553
queue_url = queue.get_runtime_attr("queue_url")
540554

@@ -591,6 +605,19 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
591605
resources.append(subscription)
592606
return resources
593607

608+
def _check_fifo_topic(
609+
self,
610+
topic_id: Optional[str],
611+
template: Optional[Dict[str, Any]],
612+
intrinsics_resolver: IntrinsicsResolver,
613+
) -> bool:
614+
if not topic_id or not template:
615+
return False
616+
617+
resources = template.get("Resources", {})
618+
properties = resources.get(topic_id, {}).get("Properties", {})
619+
return intrinsics_resolver.resolve_parameter_refs(properties.get("FifoTopic", False)) # type: ignore[no-any-return]
620+
594621
def _inject_subscription( # noqa: PLR0913
595622
self,
596623
protocol: str,
@@ -621,8 +648,12 @@ def _inject_subscription( # noqa: PLR0913
621648

622649
return subscription
623650

624-
def _inject_sqs_queue(self, function): # type: ignore[no-untyped-def]
625-
return SQSQueue(self.logical_id + "Queue", attributes=function.get_passthrough_resource_attributes())
651+
def _inject_sqs_queue(self, function, fifo_topic=False): # type: ignore[no-untyped-def]
652+
queue = SQSQueue(self.logical_id + "Queue", attributes=function.get_passthrough_resource_attributes())
653+
654+
if fifo_topic:
655+
queue.FifoQueue = fifo_topic
656+
return queue
626657

627658
def _inject_sqs_event_source_mapping(self, function, role, queue_arn, batch_size=None, enabled=None): # type: ignore[no-untyped-def]
628659
event_source = SQS(

samtranslator/model/sam_resources.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
351351
kwargs["event_resources"],
352352
intrinsics_resolver,
353353
lambda_alias=lambda_alias,
354+
original_template=kwargs.get("original_template"),
354355
)
355356
except InvalidEventException as e:
356357
raise InvalidResourceException(self.logical_id, e.message) from e
@@ -775,13 +776,14 @@ def order_events(event: Tuple[str, Any]) -> Any:
775776
return logical_id
776777
return event_dict.get("Properties", {}).get("Path", logical_id)
777778

778-
def _generate_event_resources(
779+
def _generate_event_resources( # noqa: PLR0913
779780
self,
780781
lambda_function: LambdaFunction,
781782
execution_role: Optional[IAMRole],
782783
event_resources: Any,
783784
intrinsics_resolver: IntrinsicsResolver,
784785
lambda_alias: Optional[LambdaAlias] = None,
786+
original_template: Optional[Dict[str, Any]] = None,
785787
) -> List[Any]:
786788
"""Generates and returns the resources associated with this function's events.
787789
@@ -811,6 +813,7 @@ def _generate_event_resources(
811813
"function": lambda_alias or lambda_function,
812814
"role": execution_role,
813815
"intrinsics_resolver": intrinsics_resolver,
816+
"original_template": original_template,
814817
}
815818

816819
for name, resource in event_resources[logical_id].items():

samtranslator/model/sqs.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22

33
from samtranslator.model import GeneratedProperty, PropertyType, Resource
44
from samtranslator.model.intrinsics import fnGetAtt, ref
5+
from samtranslator.model.types import PassThrough
56

67

78
class SQSQueue(Resource):
89
resource_type = "AWS::SQS::Queue"
9-
property_types: Dict[str, PropertyType] = {"Tags": GeneratedProperty()}
10+
property_types: Dict[str, PropertyType] = {
11+
"FifoQueue": GeneratedProperty(),
12+
"Tags": GeneratedProperty(),
13+
}
1014
runtime_attrs = {
1115
"queue_url": lambda self: ref(self.logical_id),
1216
"arn": lambda self: fnGetAtt(self.logical_id, "Arn"),
1317
}
1418

19+
FifoQueue: PassThrough
20+
1521

1622
class SQSQueuePolicy(Resource):
1723
resource_type = "AWS::SQS::QueuePolicy"

samtranslator/model/stepfunctions/events.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from abc import ABCMeta
3-
from typing import Any, Dict, Optional, cast
3+
from typing import Any, Dict, List, Optional, Union, cast
44

55
from samtranslator.metrics.method_decorator import cw_timer
66
from samtranslator.model import Property, PropertyType, Resource, ResourceMacro
@@ -10,6 +10,7 @@
1010
from samtranslator.model.exceptions import InvalidEventException
1111
from samtranslator.model.iam import IAMRole, IAMRolePolicies
1212
from samtranslator.model.intrinsics import fnSub
13+
from samtranslator.model.stepfunctions.resources import StepFunctionsStateMachine
1314
from samtranslator.model.types import IS_BOOL, IS_DICT, IS_STR, PassThrough
1415
from samtranslator.swagger.swagger import SwaggerEditor
1516
from samtranslator.translator import logical_id_generator
@@ -50,7 +51,13 @@ def _generate_logical_id(self, prefix, suffix, resource_type): # type: ignore[n
5051
generator = logical_id_generator.LogicalIdGenerator(prefix + resource_type, suffix)
5152
return generator.gen()
5253

53-
def _construct_role(self, resource, permissions_boundary=None, prefix=None, suffix=""): # type: ignore[no-untyped-def]
54+
def _construct_role(
55+
self,
56+
resource: StepFunctionsStateMachine,
57+
permissions_boundary: Optional[str],
58+
prefix: Optional[str],
59+
suffix: str = "",
60+
) -> IAMRole:
5461
"""Constructs the IAM Role resource allowing the event service to invoke
5562
the StartExecution API of the state machine resource it is associated with.
5663
@@ -93,6 +100,7 @@ class Schedule(EventSource):
93100
"DeadLetterConfig": PropertyType(False, IS_DICT),
94101
"RetryPolicy": PropertyType(False, IS_DICT),
95102
"Target": Property(False, IS_DICT),
103+
"RoleArn": Property(False, IS_STR),
96104
}
97105

98106
Schedule: PassThrough
@@ -104,6 +112,7 @@ class Schedule(EventSource):
104112
DeadLetterConfig: Optional[Dict[str, Any]]
105113
RetryPolicy: Optional[PassThrough]
106114
Target: Optional[PassThrough]
115+
RoleArn: Optional[PassThrough]
107116

108117
@cw_timer(prefix=SFN_EVETSOURCE_METRIC_PREFIX)
109118
def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
@@ -113,7 +122,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
113122
:returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands
114123
:rtype: list
115124
"""
116-
resources = []
125+
resources: List[Any] = []
117126

118127
permissions_boundary = kwargs.get("permissions_boundary")
119128

@@ -135,8 +144,12 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
135144
events_rule.Name = self.Name
136145
events_rule.Description = self.Description
137146

138-
role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
139-
resources.append(role)
147+
role: Union[IAMRole, str, Dict[str, Any]]
148+
if self.RoleArn is None:
149+
role = self._construct_role(resource, permissions_boundary, prefix=None)
150+
resources.append(role)
151+
else:
152+
role = self.RoleArn
140153

141154
source_arn = events_rule.get_runtime_attr("arn")
142155
dlq_queue_arn = None
@@ -146,26 +159,44 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
146159
self, source_arn, passthrough_resource_attributes
147160
)
148161
resources.extend(dlq_resources)
149-
events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] # type: ignore[no-untyped-call]
162+
events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)]
150163

151164
return resources
152165

153-
def _construct_target(self, resource, role, dead_letter_queue_arn=None): # type: ignore[no-untyped-def]
154-
"""Constructs the Target property for the EventBridge Rule.
155-
156-
:returns: the Target property
157-
:rtype: dict
166+
def _construct_target(
167+
self,
168+
resource: StepFunctionsStateMachine,
169+
role: Union[IAMRole, str, Dict[str, Any]],
170+
dead_letter_queue_arn: Optional[str],
171+
) -> Dict[str, Any]:
172+
"""_summary_
173+
174+
Parameters
175+
----------
176+
resource
177+
StepFunctionsState machine resource to be generated
178+
role
179+
The role to be used by the Schedule event resource either generated or user provides arn
180+
dead_letter_queue_arn
181+
Dead letter queue associated with the resource
182+
183+
Returns
184+
-------
185+
The Target property
158186
"""
159187
target_id = (
160188
self.Target["Id"]
161189
if self.Target and "Id" in self.Target
162190
else generate_valid_target_id(self.logical_id, EVENT_RULE_SFN_TARGET_SUFFIX)
163191
)
192+
164193
target = {
165194
"Arn": resource.get_runtime_attr("arn"),
166195
"Id": target_id,
167-
"RoleArn": role.get_runtime_attr("arn"),
168196
}
197+
198+
target["RoleArn"] = role.get_runtime_attr("arn") if isinstance(role, IAMRole) else role
199+
169200
if self.Input is not None:
170201
target["Input"] = self.Input
171202

@@ -216,7 +247,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
216247
:returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events/EventBridge event expands
217248
:rtype: list
218249
"""
219-
resources = []
250+
resources: List[Any] = []
220251

221252
permissions_boundary = kwargs.get("permissions_boundary")
222253

@@ -231,7 +262,11 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
231262

232263
resources.append(events_rule)
233264

234-
role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
265+
role = self._construct_role(
266+
resource,
267+
permissions_boundary,
268+
prefix=None,
269+
)
235270
resources.append(role)
236271

237272
source_arn = events_rule.get_runtime_attr("arn")
@@ -331,7 +366,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
331366
:returns: a list of vanilla CloudFormation Resources, to which this Api event expands
332367
:rtype: list
333368
"""
334-
resources = []
369+
resources: List[Any] = []
335370

336371
intrinsics_resolver = kwargs.get("intrinsics_resolver")
337372
permissions_boundary = kwargs.get("permissions_boundary")
@@ -340,7 +375,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
340375
# Convert to lower case so that user can specify either GET or get
341376
self.Method = self.Method.lower()
342377

343-
role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
378+
role = self._construct_role(resource, permissions_boundary, prefix=None)
344379
resources.append(role)
345380

346381
explicit_api = kwargs["explicit_api"]

samtranslator/plugins/application/serverless_app_plugin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
import json
33
import logging
4+
import re
45
from time import sleep
56
from typing import Any, Callable, Dict, List, Optional, Tuple
67

@@ -150,6 +151,14 @@ def on_before_transform_template(self, template_dict): # type: ignore[no-untype
150151
raise InvalidResourceException(
151152
logical_id, "Serverless Application Repository is not available in this region."
152153
)
154+
# SSM Pattern found here https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html
155+
ssm_pattern = r"{{resolve:ssm:[a-zA-Z0-9_.\-/]+(:\d+)?}}"
156+
if re.search(ssm_pattern, app_id):
157+
raise InvalidResourceException(
158+
logical_id,
159+
"Serverless Application Repostiory does not support dynamic reference in 'ApplicationId' property.",
160+
)
161+
153162
self._make_service_call_with_retry(service_call, app_id, semver, key, logical_id) # type: ignore[no-untyped-call]
154163
except InvalidResourceException as e:
155164
# Catch all InvalidResourceExceptions, raise those in the before_resource_transform target.

0 commit comments

Comments
 (0)