Skip to content

Commit

Permalink
Merge branch 'develop' into tmp/1696541307/main
Browse files Browse the repository at this point in the history
  • Loading branch information
GavinZZ authored Oct 5, 2023
2 parents fff0213 + 416e76b commit 4fd999b
Show file tree
Hide file tree
Showing 22 changed files with 1,887 additions and 129 deletions.
5 changes: 4 additions & 1 deletion integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ def _delete_unused_network_interface_by_subnet(ec2_client, subnet_id):
network_interface_ids += [ni["NetworkInterfaceId"] for ni in page["NetworkInterfaces"]]

for ni_id in network_interface_ids:
ec2_client.delete_network_interface(NetworkInterfaceId=ni_id)
try:
ec2_client.delete_network_interface(NetworkInterfaceId=ni_id)
except ClientError as e:
LOG.error("Unable to delete network interface %s", ni_id, exc_info=e)
time.sleep(0.5)

LOG.info("Deleted %s unused network interfaces under subnet %s", len(network_interface_ids), subnet_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ScheduleEventProperties(BaseModel):
Schedule: Optional[PassThroughProp] = scheduleeventproperties("Schedule")
State: Optional[PassThroughProp] = scheduleeventproperties("State")
Target: Optional[ScheduleTarget] = scheduleeventproperties("Target")
RoleArn: Optional[PassThroughProp] # TODO: add doc


class ScheduleEvent(BaseModel):
Expand Down
39 changes: 35 additions & 4 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
from samtranslator.model.eventsources import FUNCTION_EVETSOURCE_METRIC_PREFIX
from samtranslator.model.eventsources.pull import SQS
from samtranslator.model.exceptions import InvalidDocumentException, InvalidEventException, InvalidResourceException
from samtranslator.model.intrinsics import fnGetAtt, fnSub, is_intrinsic, make_conditional, make_shorthand, ref
from samtranslator.model.intrinsics import (
fnGetAtt,
fnSub,
get_logical_id_from_intrinsic,
is_intrinsic,
make_conditional,
make_shorthand,
ref,
)
from samtranslator.model.iot import IotTopicRule
from samtranslator.model.lambda_ import LambdaPermission
from samtranslator.model.s3 import S3Bucket
Expand Down Expand Up @@ -517,6 +525,8 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
if not function:
raise TypeError("Missing required keyword argument: function")

intrinsics_resolver: IntrinsicsResolver = kwargs["intrinsics_resolver"]

# SNS -> Lambda
if not self.SqsSubscription:
subscription = self._inject_subscription(
Expand All @@ -534,7 +544,11 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
# SNS -> SQS(Create New) -> Lambda
if isinstance(self.SqsSubscription, bool):
resources = [] # type: ignore[var-annotated]
queue = self._inject_sqs_queue(function) # type: ignore[no-untyped-call]

fifo_topic = self._check_fifo_topic(
get_logical_id_from_intrinsic(self.Topic), kwargs.get("original_template"), intrinsics_resolver
)
queue = self._inject_sqs_queue(function, fifo_topic) # type: ignore[no-untyped-call]
queue_arn = queue.get_runtime_attr("arn")
queue_url = queue.get_runtime_attr("queue_url")

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

def _check_fifo_topic(
self,
topic_id: Optional[str],
template: Optional[Dict[str, Any]],
intrinsics_resolver: IntrinsicsResolver,
) -> bool:
if not topic_id or not template:
return False

resources = template.get("Resources", {})
properties = resources.get(topic_id, {}).get("Properties", {})
return intrinsics_resolver.resolve_parameter_refs(properties.get("FifoTopic", False)) # type: ignore[no-any-return]

def _inject_subscription( # noqa: PLR0913
self,
protocol: str,
Expand Down Expand Up @@ -621,8 +648,12 @@ def _inject_subscription( # noqa: PLR0913

return subscription

def _inject_sqs_queue(self, function): # type: ignore[no-untyped-def]
return SQSQueue(self.logical_id + "Queue", attributes=function.get_passthrough_resource_attributes())
def _inject_sqs_queue(self, function, fifo_topic=False): # type: ignore[no-untyped-def]
queue = SQSQueue(self.logical_id + "Queue", attributes=function.get_passthrough_resource_attributes())

if fifo_topic:
queue.FifoQueue = fifo_topic
return queue

def _inject_sqs_event_source_mapping(self, function, role, queue_arn, batch_size=None, enabled=None): # type: ignore[no-untyped-def]
event_source = SQS(
Expand Down
5 changes: 4 additions & 1 deletion samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
kwargs["event_resources"],
intrinsics_resolver,
lambda_alias=lambda_alias,
original_template=kwargs.get("original_template"),
)
except InvalidEventException as e:
raise InvalidResourceException(self.logical_id, e.message) from e
Expand Down Expand Up @@ -775,13 +776,14 @@ def order_events(event: Tuple[str, Any]) -> Any:
return logical_id
return event_dict.get("Properties", {}).get("Path", logical_id)

def _generate_event_resources(
def _generate_event_resources( # noqa: PLR0913
self,
lambda_function: LambdaFunction,
execution_role: Optional[IAMRole],
event_resources: Any,
intrinsics_resolver: IntrinsicsResolver,
lambda_alias: Optional[LambdaAlias] = None,
original_template: Optional[Dict[str, Any]] = None,
) -> List[Any]:
"""Generates and returns the resources associated with this function's events.
Expand Down Expand Up @@ -811,6 +813,7 @@ def _generate_event_resources(
"function": lambda_alias or lambda_function,
"role": execution_role,
"intrinsics_resolver": intrinsics_resolver,
"original_template": original_template,
}

for name, resource in event_resources[logical_id].items():
Expand Down
8 changes: 7 additions & 1 deletion samtranslator/model/sqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

from samtranslator.model import GeneratedProperty, PropertyType, Resource
from samtranslator.model.intrinsics import fnGetAtt, ref
from samtranslator.model.types import PassThrough


class SQSQueue(Resource):
resource_type = "AWS::SQS::Queue"
property_types: Dict[str, PropertyType] = {"Tags": GeneratedProperty()}
property_types: Dict[str, PropertyType] = {
"FifoQueue": GeneratedProperty(),
"Tags": GeneratedProperty(),
}
runtime_attrs = {
"queue_url": lambda self: ref(self.logical_id),
"arn": lambda self: fnGetAtt(self.logical_id, "Arn"),
}

FifoQueue: PassThrough


class SQSQueuePolicy(Resource):
resource_type = "AWS::SQS::QueuePolicy"
Expand Down
67 changes: 51 additions & 16 deletions samtranslator/model/stepfunctions/events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from abc import ABCMeta
from typing import Any, Dict, Optional, cast
from typing import Any, Dict, List, Optional, Union, cast

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

def _construct_role(self, resource, permissions_boundary=None, prefix=None, suffix=""): # type: ignore[no-untyped-def]
def _construct_role(
self,
resource: StepFunctionsStateMachine,
permissions_boundary: Optional[str],
prefix: Optional[str],
suffix: str = "",
) -> IAMRole:
"""Constructs the IAM Role resource allowing the event service to invoke
the StartExecution API of the state machine resource it is associated with.
Expand Down Expand Up @@ -93,6 +100,7 @@ class Schedule(EventSource):
"DeadLetterConfig": PropertyType(False, IS_DICT),
"RetryPolicy": PropertyType(False, IS_DICT),
"Target": Property(False, IS_DICT),
"RoleArn": Property(False, IS_STR),
}

Schedule: PassThrough
Expand All @@ -104,6 +112,7 @@ class Schedule(EventSource):
DeadLetterConfig: Optional[Dict[str, Any]]
RetryPolicy: Optional[PassThrough]
Target: Optional[PassThrough]
RoleArn: Optional[PassThrough]

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

permissions_boundary = kwargs.get("permissions_boundary")

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

role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
resources.append(role)
role: Union[IAMRole, str, Dict[str, Any]]
if self.RoleArn is None:
role = self._construct_role(resource, permissions_boundary, prefix=None)
resources.append(role)
else:
role = self.RoleArn

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

return resources

def _construct_target(self, resource, role, dead_letter_queue_arn=None): # type: ignore[no-untyped-def]
"""Constructs the Target property for the EventBridge Rule.
:returns: the Target property
:rtype: dict
def _construct_target(
self,
resource: StepFunctionsStateMachine,
role: Union[IAMRole, str, Dict[str, Any]],
dead_letter_queue_arn: Optional[str],
) -> Dict[str, Any]:
"""_summary_
Parameters
----------
resource
StepFunctionsState machine resource to be generated
role
The role to be used by the Schedule event resource either generated or user provides arn
dead_letter_queue_arn
Dead letter queue associated with the resource
Returns
-------
The Target property
"""
target_id = (
self.Target["Id"]
if self.Target and "Id" in self.Target
else generate_valid_target_id(self.logical_id, EVENT_RULE_SFN_TARGET_SUFFIX)
)

target = {
"Arn": resource.get_runtime_attr("arn"),
"Id": target_id,
"RoleArn": role.get_runtime_attr("arn"),
}

target["RoleArn"] = role.get_runtime_attr("arn") if isinstance(role, IAMRole) else role

if self.Input is not None:
target["Input"] = self.Input

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

permissions_boundary = kwargs.get("permissions_boundary")

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

resources.append(events_rule)

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

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

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

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

explicit_api = kwargs["explicit_api"]
Expand Down
9 changes: 9 additions & 0 deletions samtranslator/plugins/application/serverless_app_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import json
import logging
import re
from time import sleep
from typing import Any, Callable, Dict, List, Optional, Tuple

Expand Down Expand Up @@ -150,6 +151,14 @@ def on_before_transform_template(self, template_dict): # type: ignore[no-untype
raise InvalidResourceException(
logical_id, "Serverless Application Repository is not available in this region."
)
# SSM Pattern found here https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html
ssm_pattern = r"{{resolve:ssm:[a-zA-Z0-9_.\-/]+(:\d+)?}}"
if re.search(ssm_pattern, app_id):
raise InvalidResourceException(
logical_id,
"Serverless Application Repostiory does not support dynamic reference in 'ApplicationId' property.",
)

self._make_service_call_with_retry(service_call, app_id, semver, key, logical_id) # type: ignore[no-untyped-call]
except InvalidResourceException as e:
# Catch all InvalidResourceExceptions, raise those in the before_resource_transform target.
Expand Down
Loading

0 comments on commit 4fd999b

Please sign in to comment.