diff --git a/.pylintrc b/.pylintrc index e4ccaec3f..685ee7053 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ # pygtk.require(). #init-hook= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignore list. They should be base names, not # paths. ignore=compat.py diff --git a/README.md b/README.md index 244a2ed2b..0b5f186bd 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Read the [SAM Documentation Contribution Guide](https://github.com/awsdocs/aws-s started. ### Join the SAM Community on Slack -[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-h82odes6-qYN2Cxit7hBGIvC6oMjGpg) on Slack to collaborate with fellow community members and the AWS SAM team. +[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-idww18e8-Z1kXhI7GNuDewkweCF3YjA) on Slack to collaborate with fellow community members and the AWS SAM team. diff --git a/docs/cloudformation_compatibility.rst b/docs/cloudformation_compatibility.rst index 5cac0920a..e5d2d3473 100644 --- a/docs/cloudformation_compatibility.rst +++ b/docs/cloudformation_compatibility.rst @@ -169,6 +169,8 @@ CloudWatchEvent (superseded by EventBridgeRule, see below) Pattern All Input All InputPath All +DeadLetterConfig All +RetryPolicy All ======================== ================================== ======================== EventBridgeRule @@ -179,6 +181,8 @@ EventBridgeRule Pattern All Input All InputPath All +DeadLetterConfig All +RetryPolicy All ======================== ================================== ======================== IotRule diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst index 3d03cd148..8b2760d61 100644 --- a/docs/internals/generated_resources.rst +++ b/docs/internals/generated_resources.rst @@ -455,6 +455,8 @@ Example: Type: Schedule Properties: Input: rate(5 minutes) + DeadLetterConfig: + Type: SQS ... Additional generated resources: @@ -464,6 +466,8 @@ CloudFormation Resource Type Logical ID ================================== ================================ AWS::Lambda::Permission MyFunction\ **MyTimer**\ Permission AWS::Events::Rule MyFunction\ **MyTimer** +AWS::SQS::Queue MyFunction\ **MyTimer**\ Queue +AWS::SQS::QueuePolicy MyFunction\ **MyTimer**\ QueuePolicy ================================== ================================ CloudWatchEvent (superseded by EventBridgeRule, see below) @@ -523,6 +527,11 @@ Example: detail: state: - terminated + DeadLetterConfig: + Type: SQS + RetryPolicy: + MaximumEventAgeInSeconds: 600 + MaximumRetryAttempts:3 ... Additional generated resources: @@ -532,6 +541,8 @@ CloudFormation Resource Type Logical ID ================================== ================================ AWS::Lambda::Permission MyFunction\ **OnTerminate**\ Permission AWS::Events::Rule MyFunction\ **OnTerminate** +AWS::SQS::Queue MyFunction\ **OnTerminate**\ Queue +AWS::SQS::QueuePolicy MyFunction\ **OnTerminate**\ QueuePolicy ================================== ================================ AWS::Serverless::Api diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index f31dd0478..4b449f5b1 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.33.0" +__version__ = "1.34.0" diff --git a/samtranslator/intrinsics/resolver.py b/samtranslator/intrinsics/resolver.py index 0a2c23787..519f856fe 100644 --- a/samtranslator/intrinsics/resolver.py +++ b/samtranslator/intrinsics/resolver.py @@ -1,6 +1,7 @@ # Help resolve intrinsic functions from samtranslator.intrinsics.actions import Action, SubAction, RefAction, GetAttAction +from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException # All intrinsics are supported by default DEFAULT_SUPPORTED_INTRINSICS = {action.intrinsic_name: action() for action in [RefAction, SubAction, GetAttAction]} @@ -17,7 +18,9 @@ def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS """ if parameters is None or not isinstance(parameters, dict): - raise TypeError("parameters must be a valid dictionary") + raise InvalidDocumentException( + [InvalidTemplateException("'Mappings' or 'Parameters' is either null or not a valid dictionary.")] + ) if not isinstance(supported_intrinsics, dict) or not all( [isinstance(value, Action) for value in supported_intrinsics.values()] diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 2c57d86a5..d9e07d5d6 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -112,6 +112,8 @@ def _construct_http_api(self): if self.disable_execute_api_endpoint is not None: self._add_endpoint_configuration() + self._add_description() + if self.definition_uri: http_api.BodyS3Location = self._construct_body_s3_dict() elif self.definition_body: @@ -124,9 +126,6 @@ def _construct_http_api(self): "add a 'HttpApi' event to an 'AWS::Serverless::Function'.", ) - if self.description: - http_api.Description = self.description - return http_api def _add_endpoint_configuration(self): @@ -586,6 +585,27 @@ def _construct_stage(self): return stage + def _add_description(self): + """Add description to DefinitionBody if Description property is set in SAM""" + if not self.description: + return + + if not self.definition_body: + raise InvalidResourceException( + self.logical_id, + "Description works only with inline OpenApi specified in the 'DefinitionBody' property.", + ) + if self.definition_body.get("info", {}).get("description"): + raise InvalidResourceException( + self.logical_id, + "Unable to set Description because it is already defined within inline OpenAPI specified in the " + "'DefinitionBody' property.", + ) + + open_api_editor = OpenApiEditor(self.definition_body) + open_api_editor.add_description(self.description) + self.definition_body = open_api_editor.openapi + def to_cloudformation(self): """Generates CloudFormation resources from a SAM HTTP API resource diff --git a/samtranslator/model/eventbridge_utils.py b/samtranslator/model/eventbridge_utils.py new file mode 100644 index 000000000..39bf40745 --- /dev/null +++ b/samtranslator/model/eventbridge_utils.py @@ -0,0 +1,54 @@ +from samtranslator.model.sqs import SQSQueue, SQSQueuePolicy, SQSQueuePolicies +from samtranslator.model.exceptions import InvalidEventException + + +class EventBridgeRuleUtils: + @staticmethod + def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None): + resources = [] + + queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue") + dlq_queue_arn = queue.get_runtime_attr("arn") + dlq_queue_url = queue.get_runtime_attr("queue_url") + + # grant necessary permission to Eventbridge Rule resource for sending messages to dead-letter queue + policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy") + policy.PolicyDocument = SQSQueuePolicies.eventbridge_dlq_send_message_resource_based_policy( + rule_arn, dlq_queue_arn + ) + policy.Queues = [dlq_queue_url] + + resources.append(queue) + resources.append(policy) + + return resources + + @staticmethod + def validate_dlq_config(source_logical_id, dead_letter_config): + supported_types = ["SQS"] + is_arn_defined = "Arn" in dead_letter_config + is_type_defined = "Type" in dead_letter_config + if is_arn_defined and is_type_defined: + raise InvalidEventException( + source_logical_id, "You can either define 'Arn' or 'Type' property of DeadLetterConfig" + ) + if is_type_defined and dead_letter_config.get("Type") not in supported_types: + raise InvalidEventException( + source_logical_id, + "The only valid value for 'Type' property of DeadLetterConfig is 'SQS'", + ) + if not is_arn_defined and not is_type_defined: + raise InvalidEventException(source_logical_id, "No 'Arn' or 'Type' property provided for DeadLetterConfig") + + @staticmethod + def get_dlq_queue_arn_and_resources(cw_event_source, source_arn): + """returns dlq queue arn and dlq_resources, assuming cw_event_source.DeadLetterConfig has been validated""" + dlq_queue_arn = cw_event_source.DeadLetterConfig.get("Arn") + if dlq_queue_arn is not None: + return dlq_queue_arn, [] + queue_logical_id = cw_event_source.DeadLetterConfig.get("QueueLogicalId") + dlq_resources = EventBridgeRuleUtils.create_dead_letter_queue_with_policy( + cw_event_source.logical_id, source_arn, queue_logical_id + ) + dlq_queue_arn = dlq_resources[0].get_runtime_attr("arn") + return dlq_queue_arn, dlq_resources diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 2739d2196..747968bb9 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -34,12 +34,17 @@ class PullEventSource(ResourceMacro): "Broker": PropertyType(False, is_str()), "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), + "SecretsManagerKmsKeyId": PropertyType(False, is_str()), "TumblingWindowInSeconds": PropertyType(False, is_type(int)), + "FunctionResponseTypes": PropertyType(False, is_type(list)), } def get_policy_arn(self): raise NotImplementedError("Subclass must implement this method") + def get_policy_statements(self): + raise NotImplementedError("Subclass must implement this method") + def to_cloudformation(self, **kwargs): """Returns the Lambda EventSourceMapping to which this pull event corresponds. Adds the appropriate managed policy to the function's execution role, if such a role is provided. @@ -87,6 +92,7 @@ def to_cloudformation(self, **kwargs): lambda_eventsourcemapping.Queues = self.Queues lambda_eventsourcemapping.SourceAccessConfigurations = self.SourceAccessConfigurations lambda_eventsourcemapping.TumblingWindowInSeconds = self.TumblingWindowInSeconds + lambda_eventsourcemapping.FunctionResponseTypes = self.FunctionResponseTypes destination_config_policy = None if self.DestinationConfig: @@ -131,8 +137,17 @@ def _link_policy(self, role, destination_config_policy=None): :param model.iam.IAMRole role: the execution role generated for the function """ policy_arn = self.get_policy_arn() - if role is not None and policy_arn not in role.ManagedPolicyArns: - role.ManagedPolicyArns.append(policy_arn) + policy_statements = self.get_policy_statements() + if role is not None: + if policy_arn is not None and policy_arn not in role.ManagedPolicyArns: + role.ManagedPolicyArns.append(policy_arn) + if policy_statements is not None: + if role.Policies is None: + role.Policies = [] + for policy in policy_statements: + if policy not in role.Policies: + if not policy.get("PolicyDocument") in [d["PolicyDocument"] for d in role.Policies]: + role.Policies.append(policy) # add SQS or SNS policy only if role is present in kwargs if role is not None and destination_config_policy is not None and destination_config_policy: if role.Policies is None: @@ -152,6 +167,9 @@ class Kinesis(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaKinesisExecutionRole") + def get_policy_statements(self): + return None + class DynamoDB(PullEventSource): """DynamoDB Streams event source.""" @@ -161,6 +179,9 @@ class DynamoDB(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaDynamoDBExecutionRole") + def get_policy_statements(self): + return None + class SQS(PullEventSource): """SQS Queue event source.""" @@ -170,6 +191,9 @@ class SQS(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaSQSQueueExecutionRole") + def get_policy_statements(self): + return None + class MSK(PullEventSource): """MSK event source.""" @@ -179,6 +203,9 @@ class MSK(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaMSKExecutionRole") + def get_policy_statements(self): + return None + class MQ(PullEventSource): """MQ event source.""" @@ -186,4 +213,59 @@ class MQ(PullEventSource): resource_type = "MQ" def get_policy_arn(self): - return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaAMQExecutionRole") + return None + + def get_policy_statements(self): + if not self.SourceAccessConfigurations: + raise InvalidEventException( + self.relative_id, + "No SourceAccessConfigurations for ActiveMQ provided.", + ) + if not type(self.SourceAccessConfigurations) is list: + raise InvalidEventException( + self.relative_id, + "Provided SourceAccessConfigurations cannot be parsed into a list.", + ) + # MQ only supports SourceAccessConfigurations with list size of 1 + if not (len(self.SourceAccessConfigurations) == 1): + raise InvalidEventException( + self.relative_id, + "SourceAccessConfigurations for ActiveMQ only supports single configuration entry.", + ) + if not self.SourceAccessConfigurations[0].get("URI"): + raise InvalidEventException( + self.relative_id, + "No URI property specified in SourceAccessConfigurations for ActiveMQ.", + ) + document = { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue", + ], + "Effect": "Allow", + "Resource": self.SourceAccessConfigurations[0].get("URI"), + }, + { + "Action": [ + "mq:DescribeBroker", + ], + "Effect": "Allow", + "Resource": self.Broker, + }, + ] + }, + } + if self.SecretsManagerKmsKeyId: + kms_policy = { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/" + + self.SecretsManagerKmsKeyId + }, + } + document["PolicyDocument"]["Statement"].append(kms_policy) + return [document] diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 170452f78..d134f1940 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -12,6 +12,7 @@ from samtranslator.model.events import EventsRule from samtranslator.model.eventsources.pull import SQS from samtranslator.model.sqs import SQSQueue, SQSQueuePolicy, SQSQueuePolicies +from samtranslator.model.eventbridge_utils import EventBridgeRuleUtils from samtranslator.model.iot import IotTopicRule from samtranslator.model.cognito import CognitoUserPool from samtranslator.translator import logical_id_generator @@ -94,6 +95,8 @@ class Schedule(PushEventSource): "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, **kwargs): @@ -118,16 +121,23 @@ def to_cloudformation(self, **kwargs): events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description - events_rule.Targets = [self._construct_target(function)] source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] + if CONDITION in function.resource_attributes: events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(self._construct_permission(function, source_arn=source_arn)) return resources - def _construct_target(self, function): + def _construct_target(self, function, dead_letter_queue_arn=None): """Constructs the Target property for the EventBridge Rule. :returns: the Target property @@ -137,6 +147,12 @@ def _construct_target(self, function): if self.Input is not None: target["Input"] = self.Input + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target @@ -148,6 +164,8 @@ class CloudWatchEvent(PushEventSource): property_types = { "EventBusName": PropertyType(False, is_str()), "Pattern": PropertyType(False, is_type(dict)), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), "Target": PropertyType(False, is_type(dict)), @@ -171,18 +189,24 @@ def to_cloudformation(self, **kwargs): events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern - events_rule.Targets = [self._construct_target(function)] + source_arn = events_rule.get_runtime_attr("arn") + + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] if CONDITION in function.resource_attributes: events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(events_rule) - - source_arn = events_rule.get_runtime_attr("arn") resources.append(self._construct_permission(function, source_arn=source_arn)) return resources - def _construct_target(self, function): + def _construct_target(self, function, dead_letter_queue_arn=None): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property @@ -195,6 +219,13 @@ def _construct_target(self, function): if self.InputPath is not None: target["InputPath"] = self.InputPath + + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target @@ -407,7 +438,7 @@ def to_cloudformation(self, **kwargs): queue_arn = queue.get_runtime_attr("arn") queue_url = queue.get_runtime_attr("queue_url") - queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url) + queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, function.resource_attributes) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) @@ -430,7 +461,9 @@ def to_cloudformation(self, **kwargs): batch_size = self.SqsSubscription.get("BatchSize", None) enabled = self.SqsSubscription.get("Enabled", None) - queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, queue_policy_logical_id) + queue_policy = self._inject_sqs_queue_policy( + self.Topic, queue_arn, queue_url, function.resource_attributes, queue_policy_logical_id + ) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) @@ -466,8 +499,11 @@ def _inject_sqs_event_source_mapping(self, function, role, queue_arn, batch_size event_source.Enabled = enabled or True return event_source.to_cloudformation(function=function, role=role) - def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, logical_id=None): + def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, resource_attributes, logical_id=None): policy = SQSQueuePolicy(logical_id or self.logical_id + "QueuePolicy") + if CONDITION in resource_attributes: + policy.set_resource_attribute(CONDITION, resource_attributes[CONDITION]) + policy.PolicyDocument = SQSQueuePolicies.sns_topic_send_message_role_policy(topic_arn, queue_arn) policy.Queues = [queue_url] return policy diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index cd829a818..c473bbd6f 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -76,6 +76,7 @@ class LambdaEventSourceMapping(Resource): "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), "TumblingWindowInSeconds": PropertyType(False, is_type(int)), + "FunctionResponseTypes": PropertyType(False, is_type(list)), } runtime_attrs = {"name": lambda self: ref(self.logical_id)} diff --git a/samtranslator/model/preferences/deployment_preference_collection.py b/samtranslator/model/preferences/deployment_preference_collection.py index d259658bb..a226eb766 100644 --- a/samtranslator/model/preferences/deployment_preference_collection.py +++ b/samtranslator/model/preferences/deployment_preference_collection.py @@ -101,9 +101,17 @@ def _codedeploy_iam_role(self): } ], } - iam_role.ManagedPolicyArns = [ - ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambda") - ] + + # CodeDeploy has a new managed policy. We cannot update any existing partitions, without customer reach out + # that support AWSCodeDeployRoleForLambda since this could regress stacks that are currently deployed. + if ArnGenerator.get_partition_name() in ["aws-iso", "aws-iso-b"]: + iam_role.ManagedPolicyArns = [ + ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambdaLimited") + ] + else: + iam_role.ManagedPolicyArns = [ + ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambda") + ] return iam_role diff --git a/samtranslator/model/s3.py b/samtranslator/model/s3.py index 6c60c26c7..57325e900 100644 --- a/samtranslator/model/s3.py +++ b/samtranslator/model/s3.py @@ -12,11 +12,15 @@ class S3Bucket(Resource): "BucketEncryption": PropertyType(False, any_type()), "BucketName": PropertyType(False, is_str()), "CorsConfiguration": PropertyType(False, any_type()), + "IntelligentTieringConfigurations": PropertyType(False, any_type()), "InventoryConfigurations": PropertyType(False, any_type()), "LifecycleConfiguration": PropertyType(False, any_type()), "LoggingConfiguration": PropertyType(False, any_type()), "MetricsConfigurations": PropertyType(False, any_type()), "NotificationConfiguration": PropertyType(False, is_type(dict)), + "ObjectLockConfiguration": PropertyType(False, any_type()), + "ObjectLockEnabled": PropertyType(False, any_type()), + "OwnershipControls": PropertyType(False, any_type()), "PublicAccessBlockConfiguration": PropertyType(False, is_type(dict)), "ReplicationConfiguration": PropertyType(False, any_type()), "Tags": PropertyType(False, is_type(list)), diff --git a/samtranslator/model/sqs.py b/samtranslator/model/sqs.py index b2691d309..cd92933db 100644 --- a/samtranslator/model/sqs.py +++ b/samtranslator/model/sqs.py @@ -19,8 +19,8 @@ class SQSQueuePolicy(Resource): class SQSQueuePolicies: - @classmethod - def sns_topic_send_message_role_policy(cls, topic_arn, queue_arn): + @staticmethod + def sns_topic_send_message_role_policy(topic_arn, queue_arn): document = { "Version": "2012-10-17", "Statement": [ @@ -34,3 +34,19 @@ def sns_topic_send_message_role_policy(cls, topic_arn, queue_arn): ], } return document + + @staticmethod + def eventbridge_dlq_send_message_resource_based_policy(rule_arn, queue_arn): + document = { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Principal": {"Service": "events.amazonaws.com"}, + "Resource": queue_arn, + "Condition": {"ArnEquals": {"aws:SourceArn": rule_arn}}, + } + ], + } + return document diff --git a/samtranslator/model/stepfunctions/events.py b/samtranslator/model/stepfunctions/events.py index e02332e20..88fe60cfe 100644 --- a/samtranslator/model/stepfunctions/events.py +++ b/samtranslator/model/stepfunctions/events.py @@ -8,6 +8,7 @@ from samtranslator.model.intrinsics import fnSub from samtranslator.translator import logical_id_generator from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException +from samtranslator.model.eventbridge_utils import EventBridgeRuleUtils from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.swagger.swagger import SwaggerEditor from samtranslator.open_api.open_api import OpenApiEditor @@ -81,6 +82,8 @@ class Schedule(EventSource): "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, resource, **kwargs): @@ -107,11 +110,18 @@ def to_cloudformation(self, resource, **kwargs): role = self._construct_role(resource, permissions_boundary) resources.append(role) - events_rule.Targets = [self._construct_target(resource, role)] + + source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] return resources - def _construct_target(self, resource, role): + def _construct_target(self, resource, role, dead_letter_queue_arn=None): """Constructs the Target property for the EventBridge Rule. :returns: the Target property @@ -125,6 +135,12 @@ def _construct_target(self, resource, role): if self.Input is not None: target["Input"] = self.Input + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target @@ -138,6 +154,8 @@ class CloudWatchEvent(EventSource): "Pattern": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, resource, **kwargs): @@ -162,11 +180,19 @@ def to_cloudformation(self, resource, **kwargs): role = self._construct_role(resource, permissions_boundary) resources.append(role) - events_rule.Targets = [self._construct_target(resource, role)] + + source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] return resources - def _construct_target(self, resource, role): + def _construct_target(self, resource, role, dead_letter_queue_arn=None): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property @@ -182,6 +208,13 @@ def _construct_target(self, resource, role): if self.InputPath is not None: target["InputPath"] = self.InputPath + + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index b31f3b752..230bd86c5 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -46,6 +46,7 @@ def __init__(self, doc): self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {}) self.definitions = self._doc.get("definitions", {}) self.tags = self._doc.get("tags", []) + self.info = self._doc.get("info", {}) def get_path(self, path): """ @@ -521,6 +522,15 @@ def add_cors( self._doc[self._X_APIGW_CORS] = cors_configuration + def add_description(self, description): + """Add description in open api definition, if it is not already defined + + :param string description: Description of the API + """ + if self.info.get("description"): + return + self.info["description"] = description + def has_api_gateway_cors(self): if self._doc.get(self._X_APIGW_CORS): return True @@ -544,6 +554,9 @@ def openapi(self): self._doc.setdefault("components", {}) self._doc["components"]["securitySchemes"] = self.security_schemes + if self.info: + self._doc["info"] = self.info + return copy.deepcopy(self._doc) @staticmethod diff --git a/samtranslator/region_configuration.py b/samtranslator/region_configuration.py index e8f7da533..712d4faf9 100644 --- a/samtranslator/region_configuration.py +++ b/samtranslator/region_configuration.py @@ -7,8 +7,6 @@ class RegionConfiguration(object): class abstracts all region/partition specific configuration. """ - partitions = {"govcloud": "aws-us-gov", "china": "aws-cn"} - @classmethod def is_apigw_edge_configuration_supported(cls): """ @@ -18,4 +16,9 @@ def is_apigw_edge_configuration_supported(cls): :return: True, if API Gateway does not support Edge configuration """ - return ArnGenerator.get_partition_name() not in [cls.partitions["govcloud"], cls.partitions["china"]] + return ArnGenerator.get_partition_name() not in [ + "aws-us-gov", + "aws-iso", + "aws-iso-b", + "aws-cn", + ] diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 04a6c02db..85b131855 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -534,6 +534,14 @@ def set_path_default_authorizer( for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): # If no integration given, then we don't need to process this definition (could be AWS::NoValue) + if not isinstance(method_definition, dict): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "{} for path {} is not a valid dictionary.".format(method_definition, path) + ) + ] + ) if not self.method_definition_has_integration(method_definition): continue existing_security = method_definition.get("security", []) @@ -548,6 +556,14 @@ def set_path_default_authorizer( # (e.g. sigv4 (AWS_IAM), api_key (API Key/Usage Plans), NONE (marker for ignoring default)) # We want to ensure only a single Authorizer security entry exists while keeping everything else for security in existing_security: + if not isinstance(security, dict): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "{} in Security for path {} is not a valid dictionary.".format(security, path) + ) + ] + ) if authorizer_names.isdisjoint(security.keys()): existing_non_authorizer_security.append(security) else: diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 633a7ba7a..b325c368e 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -39,15 +39,23 @@ def get_partition_name(cls, region=None): :param region: Optional name of the region :return: Partition name """ + if region is None: # Use Boto3 to get the region where code is running. This uses Boto's regular region resolution # mechanism, starting from AWS_DEFAULT_REGION environment variable. region = boto3.session.Session().region_name + # setting default partition to aws, this will be overwritten by checking the region below + partition = "aws" + region_string = region.lower() if region_string.startswith("cn-"): - return "aws-cn" + partition = "aws-cn" + elif region_string.startswith("us-iso-"): + partition = "aws-iso" + elif region_string.startswith("us-isob"): + partition = "aws-iso-b" elif region_string.startswith("us-gov"): - return "aws-us-gov" - else: - return "aws" + partition = "aws-us-gov" + + return partition diff --git a/samtranslator/validator/sam_schema/schema.json b/samtranslator/validator/sam_schema/schema.json index bff1d9ea3..72df7539e 100644 --- a/samtranslator/validator/sam_schema/schema.json +++ b/samtranslator/validator/sam_schema/schema.json @@ -368,6 +368,34 @@ }, "Pattern": { "type": "object" + }, + "DeadLetterConfig": { + "additionalProperties": false, + "properties": { + "Arn": { + "type": "string" + }, + "Type": { + "type": "string" + }, + "QueueLogicalId": { + "type": "string" + } + }, + "type": "object" + }, + "RetryPolicy": { + "additionalProperties": false, + "minProperties": 1, + "properties": { + "MaximumEventAgeInSeconds": { + "type": "number" + }, + "MaximumRetryAttempts": { + "type": "number" + } + }, + "type": "object" } }, "required": [ diff --git a/tests/intrinsics/test_resolver.py b/tests/intrinsics/test_resolver.py index 29d2be39b..2f62b510f 100644 --- a/tests/intrinsics/test_resolver.py +++ b/tests/intrinsics/test_resolver.py @@ -2,6 +2,7 @@ from mock import Mock, patch from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.intrinsics.actions import Action +from samtranslator.model.exceptions import InvalidDocumentException class TestParameterReferenceResolution(TestCase): @@ -101,11 +102,11 @@ def test_skip_invalid_values_for_sub(self): self.assertEqual(output, expected) def test_throw_on_empty_parameters(self): - with self.assertRaises(TypeError): + with self.assertRaises(InvalidDocumentException): IntrinsicsResolver(None).resolve_parameter_refs({}) def test_throw_on_non_dict_parameters(self): - with self.assertRaises(TypeError): + with self.assertRaises(InvalidDocumentException): IntrinsicsResolver([1, 2, 3]).resolve_parameter_refs({}) def test_short_circuit_on_empty_parameters(self): diff --git a/tests/model/eventsources/test_eventbridge_rule_source.py b/tests/model/eventsources/test_eventbridge_rule_source.py new file mode 100644 index 000000000..7e67edc8d --- /dev/null +++ b/tests/model/eventsources/test_eventbridge_rule_source.py @@ -0,0 +1,78 @@ +from mock import Mock, patch +from unittest import TestCase + +from samtranslator.model.eventsources.push import EventBridgeRule +from samtranslator.model.lambda_ import LambdaFunction +from samtranslator.model.exceptions import InvalidEventException + + +class EventBridgeRuleSourceTests(TestCase): + def setUp(self): + self.logical_id = "EventBridgeRule" + self.func = LambdaFunction("func") + + self.eb_event_source = EventBridgeRule(self.logical_id) + self.eb_event_source.Pattern = {"detail": {"state": ["terminated"]}} + + def test_target_id_when_not_provided(self): + cfn = self.eb_event_source.to_cloudformation(function=self.func) + target_id = cfn[0].Targets[0]["Id"] + self.assertEqual(target_id, "{}{}".format(self.logical_id, "LambdaTarget")) + + def test_target_id_when_provided(self): + self.eb_event_source.Target = {"Id": "MyTargetId"} + cfn = self.eb_event_source.to_cloudformation(function=self.func) + target_id = cfn[0].Targets[0]["Id"] + self.assertEqual(target_id, "MyTargetId") + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.eb_event_source.RetryPolicy = retry_policy + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[2] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[2] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/eventsources/test_schedule_event_source.py b/tests/model/eventsources/test_schedule_event_source.py new file mode 100644 index 000000000..a1be20378 --- /dev/null +++ b/tests/model/eventsources/test_schedule_event_source.py @@ -0,0 +1,66 @@ +from mock import Mock, patch +from unittest import TestCase + +from samtranslator.model.eventsources.push import Schedule +from samtranslator.model.lambda_ import LambdaFunction +from samtranslator.model.exceptions import InvalidEventException + + +class ScheduleEventSource(TestCase): + def setUp(self): + self.logical_id = "ScheduleEvent" + self.schedule_event_source = Schedule(self.logical_id) + self.schedule_event_source.Schedule = "rate(1 minute)" + self.func = LambdaFunction("func") + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.schedule_event_source.RetryPolicy = retry_policy + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/stepfunctions/test_cloudwatchevents_event.py b/tests/model/stepfunctions/test_cloudwatchevents_event.py index d60241cfa..33c74928e 100644 --- a/tests/model/stepfunctions/test_cloudwatchevents_event.py +++ b/tests/model/stepfunctions/test_cloudwatchevents_event.py @@ -1,6 +1,7 @@ from mock import Mock from unittest import TestCase from samtranslator.model.stepfunctions.events import CloudWatchEvent +from samtranslator.model.exceptions import InvalidEventException class CloudWatchEventsEventSource(TestCase): @@ -94,3 +95,55 @@ def test_to_cloudformation_with_eventbus_name(self): self.assertEqual(len(resources), 2) event_rule = resources[0] self.assertEqual(event_rule.Targets[0]["Input"], input_to_service) + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.cwe_event_source.RetryPolicy = retry_policy + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/stepfunctions/test_schedule_event.py b/tests/model/stepfunctions/test_schedule_event.py index 4082dc91a..e426f9305 100644 --- a/tests/model/stepfunctions/test_schedule_event.py +++ b/tests/model/stepfunctions/test_schedule_event.py @@ -1,6 +1,7 @@ from mock import Mock from unittest import TestCase from samtranslator.model.stepfunctions.events import Schedule +from samtranslator.model.exceptions import InvalidEventException class ScheduleEventSource(TestCase): @@ -85,3 +86,55 @@ def test_to_cloudformation_with_input(self): self.assertEqual(len(resources), 2) event_rule = resources[0] self.assertEqual(event_rule.Targets[0]["Input"], input_to_service) + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.schedule_event_source.RetryPolicy = retry_policy + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index bd6a6789d..e84c5b1ce 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -281,18 +281,54 @@ class TestHttpApiDescription(TestCase): @patch("boto3.session.Session.region_name", "eu-central-1") def test_with_no_description(self): sam_http_api = SamHttpApi("foo") - sam_http_api.DefinitionUri = "s3://foobar/foo.zip" + sam_http_api.DefinitionBody = { + "openapi": "3.0.1", + "paths": {"/foo": {}, "/bar": {}}, + "info": {"description": "existing description"}, + } resources = sam_http_api.to_cloudformation(**self.kwargs) - rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] - self.assertEqual(rest_api[0].Description, None) + http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] + self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "existing description") @patch("boto3.session.Session.region_name", "eu-central-1") - def test_with_description(self): + def test_with_no_definition_body(self): sam_http_api = SamHttpApi("foo") - sam_http_api.DefinitionUri = "s3://foobar/foo.zip" sam_http_api.Description = "my description" + with self.assertRaises(InvalidResourceException) as context: + sam_http_api.to_cloudformation(**self.kwargs) + self.assertEqual( + context.exception.message, + "Resource with id [foo] is invalid. " + "Description works only with inline OpenApi specified in the 'DefinitionBody' property.", + ) + + @patch("boto3.session.Session.region_name", "eu-central-1") + def test_with_description_defined_in_definition_body(self): + sam_http_api = SamHttpApi("foo") + sam_http_api.DefinitionBody = { + "openapi": "3.0.1", + "paths": {"/foo": {}, "/bar": {}}, + "info": {"description": "existing description"}, + } + sam_http_api.Description = "new description" + + with self.assertRaises(InvalidResourceException) as context: + sam_http_api.to_cloudformation(**self.kwargs) + self.assertEqual( + context.exception.message, + "Resource with id [foo] is invalid. " + "Unable to set Description because it is already defined within inline OpenAPI specified in the " + "'DefinitionBody' property.", + ) + + @patch("boto3.session.Session.region_name", "eu-central-1") + def test_with_description_not_defined_in_definition_body(self): + sam_http_api = SamHttpApi("foo") + sam_http_api.DefinitionBody = {"openapi": "3.0.1", "paths": {"/foo": {}}, "info": {}} + sam_http_api.Description = "new description" + resources = sam_http_api.to_cloudformation(**self.kwargs) - rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] - self.assertEqual(rest_api[0].Description, "my description") + http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] + self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description") diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index a70227e35..9938cab1c 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -414,3 +414,26 @@ def test_must_get_integration_function_if_exists(self): "HttpApiFunction", ) self.assertFalse(self.editor.get_integration_function_logical_id("/bar", "get")) + + +class TestOpenApiEdit_add_description(TestCase): + def setUp(self): + self.original_openapi_with_description = { + "openapi": "3.0.1", + "paths": {}, + "info": {"description": "Existing Description"}, + } + self.original_openapi_without_description = { + "openapi": "3.0.1", + "paths": {}, + } + + def test_must_add_description_if_not_defined(self): + editor = OpenApiEditor(self.original_openapi_without_description) + editor.add_description("New Description") + self.assertEqual(editor.openapi["info"]["description"], "New Description") + + def test_must_not_add_description_if_already_defined(self): + editor = OpenApiEditor(self.original_openapi_with_description) + editor.add_description("New Description") + self.assertEqual(editor.openapi["info"]["description"], "Existing Description") diff --git a/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml b/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml new file mode 100644 index 000000000..28a4063eb --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml @@ -0,0 +1,19 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: CloudWatchEvent + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: DlqArn + Type: SQS \ No newline at end of file diff --git a/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml b/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml new file mode 100644 index 000000000..924f74dce --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml @@ -0,0 +1,18 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: queue \ No newline at end of file diff --git a/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml b/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml new file mode 100644 index 000000000..fc0eb1b57 --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml @@ -0,0 +1,18 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + QueueLogicalId: MyDlqId \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml b/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml new file mode 100644 index 000000000..cc762882f --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml @@ -0,0 +1,15 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SQS + Arn: MyDlqArn \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml b/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml new file mode 100644 index 000000000..e2add19d3 --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml @@ -0,0 +1,14 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SNS \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml b/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml new file mode 100644 index 000000000..542e05513 --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml @@ -0,0 +1,14 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + QueueLogicalId: MyDlqId diff --git a/tests/translator/input/error_invalid_method_definition.yaml b/tests/translator/input/error_invalid_method_definition.yaml new file mode 100644 index 000000000..fd9b6617f --- /dev/null +++ b/tests/translator/input/error_invalid_method_definition.yaml @@ -0,0 +1,66 @@ +Globals: + Api: + Name: "some api" + CacheClusterEnabled: True + CacheClusterSize: "1.6" + Auth: + DefaultAuthorizer: MyCognitoAuth + Authorizers: + MyCognitoAuth: + UserPoolArn: !GetAtt MyUserPool.Arn + Variables: + SomeVar: Value + +Resources: + ImplicitApiFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: SomeStage + DefinitionBody: + swagger: 2.0 + info: + version: '1.0' + title: !Ref AWS::StackName + paths: + "/": + parameters: + - name: domain + in: path + description: Application domain + type: string + required: true + tags: + - InvalidMethodDefinition + get: + x-amazon-apigateway-integration: + httpMethod: POST + type: aws_proxy + uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ImplicitApiFunction.Arn}/invocations + responses: {} + + MyUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: UserPoolName + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + Schema: + - AttributeDataType: String + Name: email + Required: false \ No newline at end of file diff --git a/tests/translator/input/error_mappings_is_null.yaml b/tests/translator/input/error_mappings_is_null.yaml new file mode 100644 index 000000000..56eb56091 --- /dev/null +++ b/tests/translator/input/error_mappings_is_null.yaml @@ -0,0 +1,23 @@ +Mappings: + +Parameters: + Stage: + Type: String + Default: 'beta' + Deployment: + Type: String + Default: 'AllAtOnce' + Custom: + Type: String + Default: 'CustomDeployment' + +Resources: + MinimalFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + AutoPublishAlias: live + DeploymentPreference: + Type: TestDeploymentConfiguration \ No newline at end of file diff --git a/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml b/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml new file mode 100644 index 000000000..15b052181 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: SQS + Arn: MyDlqArn diff --git a/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml b/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml new file mode 100644 index 000000000..47c7bacd4 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: EventBus diff --git a/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml b/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml new file mode 100644 index 000000000..889306b0c --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + QueuelogicalId: MyDlqId diff --git a/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml b/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml new file mode 100644 index 000000000..f4faf5c58 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Arn: MyDlqArn + Type: SQS \ No newline at end of file diff --git a/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml b/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml new file mode 100644 index 000000000..b052c599b --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Type: SNS + \ No newline at end of file diff --git a/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml b/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml new file mode 100644 index 000000000..cabfb9bce --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + QueueLogicalId: MyDlq + \ No newline at end of file diff --git a/tests/translator/input/error_swagger_security_not_dict.yaml b/tests/translator/input/error_swagger_security_not_dict.yaml new file mode 100644 index 000000000..c9c69a6d4 --- /dev/null +++ b/tests/translator/input/error_swagger_security_not_dict.yaml @@ -0,0 +1,70 @@ +transformId: AWS::Serverless-2016-10-31 +AWSTemplateFormatVersion: '2010-09-09' +Resources: + AuthFunction: + Type: AWS::Serverless::Function + AccessingPartyAPI: + Type: AWS::Serverless::Api + Properties: + EndpointConfiguration: REGIONAL + StageName: demo + Auth: + DefaultAuthorizer: CustomAuthorizer + Authorizers: + CustomAuthorizer: + FunctionPayloadType: TOKEN + FunctionArn: + Fn::GetAtt: + - AuthFunction + - Arn + AddDefaultAuthorizerToCorsPreflight: false + DefinitionBody: + paths: + "/path": + put: + responses: + '201': + content: + application/json: + schema: + "$ref": "abcd" + x-amazon-apigateway-integration: + contentHandling: CONVERT_TO_TEXT + responses: + default: + statusCode: '200' + uri: + Fn::Sub: foobar + httpMethod: POST + passthroughBehavior: when_no_match + type: aws_proxy + requestBody: + content: + application/json: + schema: + required: + - readoutId + - status + type: object + security: + CustomAuthorizer: [] + + openapi: 3.0.3 + components: + securitySchemes: + CustomAuthorizer: + in: header + type: apiKey + name: Authorization + + AccessingPartyAPIFunction: + Type: AWS::Serverless::Function + Properties: + Events: + PutReservation: + Type: Api + Properties: + Path: "/path" + RestApiId: + Ref: AccessingPartyAPI + Method: put \ No newline at end of file diff --git a/tests/translator/input/eventbridgerule_with_dlq.yaml b/tests/translator/input/eventbridgerule_with_dlq.yaml new file mode 100644 index 000000000..7bb7dc59d --- /dev/null +++ b/tests/translator/input/eventbridgerule_with_dlq.yaml @@ -0,0 +1,31 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SQS + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: ARN \ No newline at end of file diff --git a/tests/translator/input/eventbridgerule_with_retry_policy.yaml b/tests/translator/input/eventbridgerule_with_retry_policy.yaml new file mode 100644 index 000000000..ed66964af --- /dev/null +++ b/tests/translator/input/eventbridgerule_with_retry_policy.yaml @@ -0,0 +1,32 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + RetryPolicy: + MaximumRetryAttempts: 3 + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 3 + MaximumEventAgeInSeconds: 200 \ No newline at end of file diff --git a/tests/translator/input/function_event_conditions.yaml b/tests/translator/input/function_event_conditions.yaml index 1a53dab8b..f30751333 100644 --- a/tests/translator/input/function_event_conditions.yaml +++ b/tests/translator/input/function_event_conditions.yaml @@ -79,6 +79,22 @@ Resources: Topic: Ref: Notifications + SNSTopicWithSQSSubscription: + Type: SNS + Properties: + Topic: + Ref: Notifications + SqsSubscription: + QueueArn: !GetAtt Queue.Arn + QueueUrl: !Ref Queue + + AnotherSNSWithSQSSubscription: + Type: SNS + Properties: + Topic: + Ref: Notifications + SqsSubscription: true + KinesisStream: Type: Kinesis Properties: @@ -99,3 +115,7 @@ Resources: Images: Type: AWS::S3::Bucket + + Queue: + Condition: MyCondition + Type: AWS::SQS::Queue diff --git a/tests/translator/input/amq.yaml b/tests/translator/input/function_with_amq.yaml similarity index 100% rename from tests/translator/input/amq.yaml rename to tests/translator/input/function_with_amq.yaml diff --git a/tests/translator/input/function_with_amq_kms.yaml b/tests/translator/input/function_with_amq_kms.yaml new file mode 100644 index 000000000..7ea02c2df --- /dev/null +++ b/tests/translator/input/function_with_amq_kms.yaml @@ -0,0 +1,18 @@ +Resources: + MQFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/queues.zip + Handler: queue.mq_handler + Runtime: python2.7 + Events: + MyMQQueue: + Type: MQ + Properties: + Broker: arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9 + Queues: + - "Queue1" + SourceAccessConfigurations: + - Type: BASIC_AUTH + URI: arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c + SecretsManagerKmsKeyId: 1abc23d4-567f-8ab9-cde0-1fab234c5d67 \ No newline at end of file diff --git a/tests/translator/input/function_with_event_source_mapping.yaml b/tests/translator/input/function_with_event_source_mapping.yaml index 769cd289d..edea3e214 100644 --- a/tests/translator/input/function_with_event_source_mapping.yaml +++ b/tests/translator/input/function_with_event_source_mapping.yaml @@ -53,6 +53,8 @@ Resources: MaximumRecordAgeInSeconds: 86400 StartingPosition: TRIM_HORIZON TumblingWindowInSeconds: 60 + FunctionResponseTypes: + - ReportBatchItemFailures DestinationConfig: OnFailure: Type: SQS diff --git a/tests/translator/input/http_api_description.yaml b/tests/translator/input/http_api_description.yaml index 6dec788ba..268584d89 100644 --- a/tests/translator/input/http_api_description.yaml +++ b/tests/translator/input/http_api_description.yaml @@ -2,7 +2,10 @@ Resources: HttpApi: Type: AWS::Serverless::HttpApi Properties: - DefinitionUri: s3://bucket/key + DefinitionBody: + openapi: "3.0.1" + paths: + "/foo": {} Description: my description Function: diff --git a/tests/translator/input/state_machine_with_eb_dlq.yaml b/tests/translator/input/state_machine_with_eb_dlq.yaml new file mode 100644 index 000000000..90721bd20 --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_dlq.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: TestDlqArn \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_eb_dlq_generated.yaml b/tests/translator/input/state_machine_with_eb_dlq_generated.yaml new file mode 100644 index 000000000..9f8d74677 --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_dlq_generated.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: SQS + QueueLogicalId: TestDLQ \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_eb_retry_policy.yaml b/tests/translator/input/state_machine_with_eb_retry_policy.yaml new file mode 100644 index 000000000..21dc2d921 --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_retry_policy.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 5 + MaximumEventAgeInSeconds: 300 \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml b/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml new file mode 100644 index 000000000..9f7385f90 --- /dev/null +++ b/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml @@ -0,0 +1,19 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Arn: Arn + RetryPolicy: + MaximumRetryAttempts: 5 + MaximumEventAgeInSeconds: 300 \ No newline at end of file diff --git a/tests/translator/output/amq.json b/tests/translator/output/amq.json deleted file mode 100644 index 796785adc..000000000 --- a/tests/translator/output/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/amq.json b/tests/translator/output/aws-cn/amq.json deleted file mode 100644 index b75267663..000000000 --- a/tests/translator/output/aws-cn/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json b/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json new file mode 100644 index 000000000..6f6fa0460 --- /dev/null +++ b/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json b/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json new file mode 100644 index 000000000..d1ab9d848 --- /dev/null +++ b/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_event_conditions.json b/tests/translator/output/aws-cn/function_event_conditions.json index c6010f940..274bbc538 100644 --- a/tests/translator/output/aws-cn/function_event_conditions.json +++ b/tests/translator/output/aws-cn/function_event_conditions.json @@ -222,7 +222,8 @@ "ManagedPolicyArns": [ "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -459,6 +460,148 @@ "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } diff --git a/tests/translator/output/aws-cn/function_with_amq.json b/tests/translator/output/aws-cn/function_with_amq.json new file mode 100644 index 000000000..6a34ed771 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_amq_kms.json b/tests/translator/output/aws-cn/function_with_amq_kms.json new file mode 100644 index 000000000..e79122065 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_event_source_mapping.json b/tests/translator/output/aws-cn/function_with_event_source_mapping.json index 9b4f44478..c566b9490 100644 --- a/tests/translator/output/aws-cn/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-cn/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/aws-cn/http_api_description.json b/tests/translator/output/aws-cn/http_api_description.json index 0798fc4e2..e5b49fcb3 100644 --- a/tests/translator/output/aws-cn/http_api_description.json +++ b/tests/translator/output/aws-cn/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json b/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json new file mode 100644 index 000000000..e49e64256 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json b/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json new file mode 100644 index 000000000..7e57f8926 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json b/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json new file mode 100644 index 000000000..bff94e177 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 000000000..5d49175a9 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/amq.json b/tests/translator/output/aws-us-gov/amq.json deleted file mode 100644 index 5a42ee959..000000000 --- a/tests/translator/output/aws-us-gov/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json b/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json new file mode 100644 index 000000000..b5dd4901b --- /dev/null +++ b/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json b/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json new file mode 100644 index 000000000..d904833c3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_event_conditions.json b/tests/translator/output/aws-us-gov/function_event_conditions.json index 2b1dea068..e22637ef5 100644 --- a/tests/translator/output/aws-us-gov/function_event_conditions.json +++ b/tests/translator/output/aws-us-gov/function_event_conditions.json @@ -222,7 +222,8 @@ "ManagedPolicyArns": [ "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -459,6 +460,148 @@ "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } diff --git a/tests/translator/output/aws-us-gov/function_with_amq.json b/tests/translator/output/aws-us-gov/function_with_amq.json new file mode 100644 index 000000000..a8a471c86 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_amq_kms.json b/tests/translator/output/aws-us-gov/function_with_amq_kms.json new file mode 100644 index 000000000..37b991a49 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json index 3e028f48e..a910aefed 100644 --- a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/aws-us-gov/http_api_description.json b/tests/translator/output/aws-us-gov/http_api_description.json index c7594a0ec..dd4a7f36c 100644 --- a/tests/translator/output/aws-us-gov/http_api_description.json +++ b/tests/translator/output/aws-us-gov/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json new file mode 100644 index 000000000..e49e64256 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json new file mode 100644 index 000000000..7e57f8926 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json new file mode 100644 index 000000000..bff94e177 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 000000000..5d49175a9 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json b/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json new file mode 100644 index 000000000..541990f40 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json b/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json new file mode 100644 index 000000000..ded9379e6 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_missing_dlq_property.json b/tests/translator/output/error_function_with_cwe_missing_dlq_property.json new file mode 100644 index 000000000..edf6a7fd7 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json b/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json new file mode 100644 index 000000000..9a90e5cf9 --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json b/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json new file mode 100644 index 000000000..d779e7ea0 --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_missing_dlq_property.json b/tests/translator/output/error_function_with_schedule_missing_dlq_property.json new file mode 100644 index 000000000..bb004d8d8 --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_invalid_method_definition.json b/tests/translator/output/error_invalid_method_definition.json new file mode 100644 index 000000000..01ff1502c --- /dev/null +++ b/tests/translator/output/error_invalid_method_definition.json @@ -0,0 +1 @@ +{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. ['InvalidMethodDefinition'] for path / is not a valid dictionary."} \ No newline at end of file diff --git a/tests/translator/output/error_mappings_is_null.json b/tests/translator/output/error_mappings_is_null.json new file mode 100644 index 000000000..04839dad3 --- /dev/null +++ b/tests/translator/output/error_mappings_is_null.json @@ -0,0 +1 @@ +{"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. 'Mappings' or 'Parameters' is either null or not a valid dictionary."} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json b/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json new file mode 100644 index 000000000..10180f357 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json b/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json new file mode 100644 index 000000000..3e132e754 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json b/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json new file mode 100644 index 000000000..39b016520 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json b/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json new file mode 100644 index 000000000..932c2adc7 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json b/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json new file mode 100644 index 000000000..c429367c2 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json b/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json new file mode 100644 index 000000000..ac6607603 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_swagger_security_not_dict.json b/tests/translator/output/error_swagger_security_not_dict.json new file mode 100644 index 000000000..9f00e5bbf --- /dev/null +++ b/tests/translator/output/error_swagger_security_not_dict.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. CustomAuthorizer in Security for path /path is not a valid dictionary." +} \ No newline at end of file diff --git a/tests/translator/output/eventbridgerule_with_dlq.json b/tests/translator/output/eventbridgerule_with_dlq.json new file mode 100644 index 000000000..2d5c96a91 --- /dev/null +++ b/tests/translator/output/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/eventbridgerule_with_retry_policy.json b/tests/translator/output/eventbridgerule_with_retry_policy.json new file mode 100644 index 000000000..2cc7e5d9c --- /dev/null +++ b/tests/translator/output/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_event_conditions.json b/tests/translator/output/function_event_conditions.json index a1c0e9a76..c6de70318 100644 --- a/tests/translator/output/function_event_conditions.json +++ b/tests/translator/output/function_event_conditions.json @@ -9,44 +9,44 @@ }, "Resources": { "MyAwesomeFunctionAliasLive": { - "Type": "AWS::Lambda::Alias", + "Type": "AWS::Lambda::Alias", "Condition": "MyCondition", "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MyAwesomeFunctionVersion640128d35d", + "MyAwesomeFunctionVersion640128d35d", "Version" ] - }, + }, "FunctionName": { "Ref": "MyAwesomeFunction" - }, + }, "Name": "Live" } - }, + }, "MyAwesomeFunctionNotificationTopicPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "sns.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "sns.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Ref": "Notifications" } } }, "MyAwesomeFunctionCWEventPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::GetAtt": [ "MyAwesomeFunctionCWEvent", @@ -73,23 +73,23 @@ } }, "MyAwesomeFunctionDDBStream": { - "Type": "AWS::Lambda::EventSourceMapping", + "Type": "AWS::Lambda::EventSourceMapping", "Condition": "MyCondition", "Properties": { - "BatchSize": 200, - "EventSourceArn": "arn:aws:dynamodb:us-west-2:012345678901:table/TestTable/stream/2015-05-11T21:21:33.291", + "BatchSize": 200, + "EventSourceArn": "arn:aws:dynamodb:us-west-2:012345678901:table/TestTable/stream/2015-05-11T21:21:33.291", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "StartingPosition": "LATEST" } }, "MyAwesomeFunctionIoTRule": { - "Type": "AWS::IoT::TopicRule", + "Type": "AWS::IoT::TopicRule", "Condition": "MyCondition", "Properties": { "TopicRulePayload": { - "AwsIotSqlVersion": "beta", + "AwsIotSqlVersion": "beta", "Actions": [ { "Lambda": { @@ -98,39 +98,39 @@ } } } - ], - "RuleDisabled": false, + ], + "RuleDisabled": false, "Sql": "SELECT * FROM 'topic/test'" } } - }, + }, "MyAwesomeFunctionKinesisStream": { - "Type": "AWS::Lambda::EventSourceMapping", + "Type": "AWS::Lambda::EventSourceMapping", "Condition": "MyCondition", "Properties": { - "BatchSize": 100, - "EventSourceArn": "arn:aws:kinesis:us-west-2:012345678901:stream/my-stream", + "BatchSize": 100, + "EventSourceArn": "arn:aws:kinesis:us-west-2:012345678901:stream/my-stream", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "StartingPosition": "TRIM_HORIZON" } }, "MyAwesomeFunctionIoTRulePermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", + "Action": "lambda:InvokeFunction", "SourceAccount": { "Fn::Sub": "${AWS::AccountId}" - }, - "Principal": "iot.amazonaws.com", + }, + "Principal": "iot.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${RuleName}", + "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${RuleName}", { "RuleName": { "Ref": "MyAwesomeFunctionIoTRule" @@ -139,58 +139,58 @@ ] } } - }, + }, "MyAwesomeFunctionNotificationTopic": { - "Type": "AWS::SNS::Subscription", + "Type": "AWS::SNS::Subscription", "Condition": "MyCondition", "Properties": { "Endpoint": { "Ref": "MyAwesomeFunctionAliasLive" - }, - "Protocol": "lambda", + }, + "Protocol": "lambda", "TopicArn": { "Ref": "Notifications" } } - }, + }, "MyAwesomeFunctionS3TriggerPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", + "Action": "lambda:InvokeFunction", "SourceAccount": { "Ref": "AWS::AccountId" - }, + }, "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "Principal": "s3.amazonaws.com" } }, "MyAwesomeFunctionCWLogPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "logs.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "logs.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${__LogGroupName__}:*", + "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${__LogGroupName__}:*", { "__LogGroupName__": "MyLogGroup" } ] } } - }, + }, "MyAwesomeFunctionEBSchedule": { - "Type": "AWS::Events::Rule", + "Type": "AWS::Events::Rule", "Condition": "MyCondition", "Properties": { - "ScheduleExpression": "rate(1 minute)", + "ScheduleExpression": "rate(1 minute)", "Targets": [ { "Id": "MyAwesomeFunctionEBScheduleLambdaTarget", @@ -200,29 +200,30 @@ } ] } - }, + }, "MyAwesomeFunctionCWLog": { - "Type": "AWS::Logs::SubscriptionFilter", + "Type": "AWS::Logs::SubscriptionFilter", "Condition": "MyCondition", "Properties": { "DestinationArn": { "Ref": "MyAwesomeFunctionAliasLive" - }, - "FilterPattern": "My pattern", + }, + "FilterPattern": "My pattern", "LogGroupName": "MyLogGroup" - }, + }, "DependsOn": [ "MyAwesomeFunctionCWLogPermission" ] - }, + }, "MyAwesomeFunctionRole": { - "Type": "AWS::IAM::Role", + "Type": "AWS::IAM::Role", "Condition": "MyCondition", "Properties": { "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -231,13 +232,13 @@ } ], "AssumeRolePolicyDocument": { - "Version": "2012-10-17", + "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" - ], - "Effect": "Allow", + ], + "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com" @@ -247,33 +248,33 @@ ] } } - }, + }, "MyAwesomeFunction": { - "Type": "AWS::Lambda::Function", + "Type": "AWS::Lambda::Function", "Condition": "MyCondition", "Properties": { "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "hello.zip" - }, - "Handler": "hello.handler", + }, + "Handler": "hello.handler", "Role": { "Fn::GetAtt": [ - "MyAwesomeFunctionRole", + "MyAwesomeFunctionRole", "Arn" ] - }, - "Runtime": "python2.7", + }, + "Runtime": "python2.7", "Tags": [ { - "Value": "SAM", + "Value": "SAM", "Key": "lambda:createdBy" } ] } }, "MyAwesomeFunctionCWEvent": { - "Type": "AWS::Events::Rule", + "Type": "AWS::Events::Rule", "Condition": "MyCondition", "Properties": { "EventPattern": { @@ -282,7 +283,7 @@ "terminated" ] } - }, + }, "Targets": [ { "Id": "MyAwesomeFunctionCWEventLambdaTarget", @@ -315,28 +316,28 @@ } }, "MyAwesomeFunctionVersion640128d35d": { - "DeletionPolicy": "Retain", - "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", "Condition": "MyCondition", "Properties": { "FunctionName": { "Ref": "MyAwesomeFunction" } } - }, + }, "Notifications": { "Condition": "MyCondition", "Type": "AWS::SNS::Topic" }, "MyAwesomeFunctionEBSchedulePermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::GetAtt": [ "MyAwesomeFunctionEBSchedule", @@ -344,7 +345,7 @@ ] } } - }, + }, "FunctionOneImageBucketPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -412,7 +413,7 @@ } }, "Images": { - "Type": "AWS::S3::Bucket", + "Type": "AWS::S3::Bucket", "Properties": { "NotificationConfiguration": { "LambdaConfigurations": [ @@ -455,10 +456,152 @@ } } ] - }, + }, "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } diff --git a/tests/translator/output/function_with_amq.json b/tests/translator/output/function_with_amq.json new file mode 100644 index 000000000..0d35f71c2 --- /dev/null +++ b/tests/translator/output/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_amq_kms.json b/tests/translator/output/function_with_amq_kms.json new file mode 100644 index 000000000..94ae8666a --- /dev/null +++ b/tests/translator/output/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/function_with_event_source_mapping.json b/tests/translator/output/function_with_event_source_mapping.json index c039a3396..2a2f668ef 100644 --- a/tests/translator/output/function_with_event_source_mapping.json +++ b/tests/translator/output/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/http_api_description.json b/tests/translator/output/http_api_description.json index 682ece8dd..fed22b685 100644 --- a/tests/translator/output/http_api_description.json +++ b/tests/translator/output/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } diff --git a/tests/translator/output/state_machine_with_eb_dlq.json b/tests/translator/output/state_machine_with_eb_dlq.json new file mode 100644 index 000000000..e49e64256 --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_eb_dlq_generated.json b/tests/translator/output/state_machine_with_eb_dlq_generated.json new file mode 100644 index 000000000..7e57f8926 --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_eb_retry_policy.json b/tests/translator/output/state_machine_with_eb_retry_policy.json new file mode 100644 index 000000000..bff94e177 --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 000000000..5d49175a9 --- /dev/null +++ b/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 2717afd6f..f380d34c3 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -153,12 +153,15 @@ class TestTranslatorEndToEnd(TestCase): "basic_layer", "cloudwatchevent", "eventbridgerule", + "eventbridgerule_with_dlq", + "eventbridgerule_with_retry_policy", "eventbridgerule_schedule_properties", "cloudwatch_logs_with_ref", "cloudwatchlog", "streams", "sqs", - "amq", + "function_with_amq", + "function_with_amq_kms", "simpletable", "simpletable_with_sse", "implicit_api", @@ -294,7 +297,11 @@ class TestTranslatorEndToEnd(TestCase): "state_machine_with_managed_policy", "state_machine_with_condition", "state_machine_with_schedule", + "state_machine_with_schedule_dlq_retry_policy", "state_machine_with_cwe", + "state_machine_with_eb_retry_policy", + "state_machine_with_eb_dlq", + "state_machine_with_eb_dlq_generated", "state_machine_with_explicit_api", "state_machine_with_implicit_api", "state_machine_with_implicit_api_globals", @@ -576,6 +583,12 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_state_machine_with_no_api_authorizers", "error_state_machine_with_undefined_api_authorizer", "error_state_machine_with_invalid_default_authorizer", + "error_state_machine_with_schedule_invalid_dlq_type", + "error_state_machine_with_schedule_both_dlq_property_provided", + "error_state_machine_with_schedule_missing_dlq_property", + "error_state_machine_with_cwe_invalid_dlq_type", + "error_state_machine_with_cwe_both_dlq_property_provided", + "error_state_machine_with_cwe_missing_dlq_property", "error_cognito_userpool_duplicate_trigger", "error_cognito_userpool_not_string", "error_api_duplicate_methods_same_path", @@ -611,6 +624,12 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_function_with_deployment_preference_missing_alias", "error_function_with_invalid_deployment_preference_hook_property", "error_function_invalid_request_parameters", + "error_function_with_schedule_invalid_dlq_type", + "error_function_with_schedule_both_dlq_property_provided", + "error_function_with_schedule_missing_dlq_property", + "error_function_with_cwe_invalid_dlq_type", + "error_function_with_cwe_both_dlq_property_provided", + "error_function_with_cwe_missing_dlq_property", "error_invalid_logical_id", "error_layer_invalid_properties", "error_missing_broker", @@ -669,6 +688,9 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_httpapi_mtls_configuration_invalid_type", "error_resource_policy_not_dict", "error_implicit_http_api_auth_any_method", + "error_invalid_method_definition", + "error_mappings_is_null", + "error_swagger_security_not_dict", ], ) @patch("boto3.session.Session.region_name", "ap-southeast-1") diff --git a/tests/unit/model/preferences/test_deployment_preference_collection.py b/tests/unit/model/preferences/test_deployment_preference_collection.py new file mode 100644 index 000000000..3412b9f5c --- /dev/null +++ b/tests/unit/model/preferences/test_deployment_preference_collection.py @@ -0,0 +1,49 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.model.preferences.deployment_preference_collection import DeploymentPreferenceCollection + + +class TestDeploymentPreferenceCollection(TestCase): + @parameterized.expand( + [ + ["aws-iso"], + ["aws-iso-b"], + ] + ) + def test_codedeploy_iam_role_contains_AWSCodeDeployRoleForLambdaLimited_managedpolicy(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + iam_role = DeploymentPreferenceCollection().codedeploy_iam_role + + self.assertIn( + "arn:{}:iam::aws:policy/service-role/AWSCodeDeployRoleForLambdaLimited".format(partition), + iam_role.ManagedPolicyArns, + ) + + @parameterized.expand( + [ + ["aws"], + ["aws-cn"], + ["aws-us-gov"], + ] + ) + def test_codedeploy_iam_role_contains_AWSCodeDeployRoleForLambda_managedpolicy(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + iam_role = DeploymentPreferenceCollection().codedeploy_iam_role + + self.assertIn( + "arn:{}:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda".format(partition), + iam_role.ManagedPolicyArns, + ) diff --git a/tests/unit/test_region_configuration.py b/tests/unit/test_region_configuration.py new file mode 100644 index 000000000..646b0c136 --- /dev/null +++ b/tests/unit/test_region_configuration.py @@ -0,0 +1,38 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.region_configuration import RegionConfiguration + + +class TestRegionConfiguration(TestCase): + @parameterized.expand( + [ + ["aws"], + ] + ) + def test_when_apigw_edge_configuration_supported(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + self.assertTrue(RegionConfiguration.is_apigw_edge_configuration_supported()) + + @parameterized.expand( + [ + ["aws-cn"], + ["aws-us-gov"], + ["aws-iso"], + ["aws-iso-b"], + ] + ) + def test_when_apigw_edge_configuration_is_not_supported(self, partition): + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + self.assertFalse(RegionConfiguration.is_apigw_edge_configuration_supported()) diff --git a/tests/unit/translator/test_arn_generator.py b/tests/unit/translator/test_arn_generator.py new file mode 100644 index 000000000..3d0d33016 --- /dev/null +++ b/tests/unit/translator/test_arn_generator.py @@ -0,0 +1,35 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.translator.arn_generator import ArnGenerator + + +class TestArnGenerator(TestCase): + @parameterized.expand( + [ + ["us-east-1", "aws"], + ["eu-west-1", "aws"], + ["cn-north-1", "aws-cn"], + ["us-gov-west-1", "aws-us-gov"], + ["us-iso-east-1", "aws-iso"], + ["us-isob-east-1", "aws-iso-b"], + ] + ) + def test_get_partition_name(self, region, expected_partition): + self.assertEqual(expected_partition, ArnGenerator.get_partition_name(region=region)) + + @parameterized.expand( + [ + ["us-east-1", "aws"], + ["eu-west-1", "aws"], + ["cn-north-1", "aws-cn"], + ["us-gov-west-1", "aws-us-gov"], + ["us-iso-east-1", "aws-iso"], + ["us-isob-east-1", "aws-iso-b"], + ] + ) + def test_get_partition_name_when_region_not_provided(self, region, expected_partition): + with patch("boto3.session.Session.region_name", region): + self.assertEqual(expected_partition, ArnGenerator.get_partition_name()) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 64ad4f2d9..baea8f300 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -513,6 +513,7 @@ MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be DestinationConfig | [Destination Config Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. +FunctionResponseTypes | `list` | Response types enabled for your function. **NOTE:** `SQSSendMessagePolicy` or `SNSPublishMessagePolicy` needs to be added in `Policies` for publishing messages to the `SQS` or `SNS` resource mentioned in `OnFailure` property @@ -536,6 +537,8 @@ Properties: Type: SQS Destination: !GetAtt MySqsQueue.Arn TumblingWindowInSeconds: 0 + FunctionResponseTypes: + - ReportBatchItemFailures ``` @@ -582,6 +585,7 @@ MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be DestinationConfig | [DestinationConfig Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. +FunctionResponseTypes | `list` | Response types enabled for your function. ##### Example: DynamoDB event source object @@ -602,6 +606,8 @@ Properties: Type: SQS Destination: !GetAtt MySqsQueue.Arn TumblingWindowInSeconds: 0 + FunctionResponseTypes + - ReportBatchItemFailures ``` #### SQS