From 66f481da834e8fe56ff9dca2dab82394288d6df4 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Tue, 22 Nov 2022 14:20:05 -0800 Subject: [PATCH 01/13] add multi-dest support for connector --- samtranslator/model/sam_resources.py | 174 +++++++++++------- tests/model/test_sam_resources.py | 6 +- .../input/connector_api_to_function.yaml | 3 +- tests/translator/output/error_connector.json | 2 +- 4 files changed, 112 insertions(+), 73 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index aa12547317..4eeb9997cf 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1770,13 +1770,15 @@ class SamConnector(SamResourceMacro): """ Source: Dict[str, Any] - Destination: Dict[str, Any] + Destination: Union[Dict[str, Any], List[Dict[str, Any]]] Permissions: List[str] resource_type = "AWS::Serverless::Connector" property_types = { "Source": PropertyType(True, dict_of(is_str(), any_type())), - "Destination": PropertyType(True, dict_of(is_str(), any_type())), + "Destination": PropertyType( + True, one_of(dict_of(is_str(), any_type()), list_of(dict_of(is_str(), any_type()))) + ), "Permissions": PropertyType(True, list_of(is_str())), } @@ -1785,86 +1787,101 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore resource_resolver: ResourceResolver = kwargs["resource_resolver"] original_template = kwargs["original_template"] - try: - destination = get_resource_reference(self.Destination, resource_resolver, self.Source) - source = get_resource_reference(self.Source, resource_resolver, self.Destination) - except ConnectorResourceError as e: - raise InvalidResourceException(self.logical_id, str(e)) - - profile = get_profile(source.resource_type, destination.resource_type) - if not profile: - raise InvalidResourceException( - self.logical_id, - f"Unable to create connector from {source.resource_type} to {destination.resource_type}; it's not supported or the template is invalid.", - ) + multi_dest = True + if isinstance(self.Destination, dict): + multi_dest = False + self.Destination = [self.Destination] - # removing duplicate permissions - self.Permissions = list(set(self.Permissions)) - profile_type, profile_properties = profile["Type"], profile["Properties"] - profile_permissions = profile_properties["AccessCategories"] - valid_permissions_combinations = profile_properties.get("ValidAccessCategories") + generated_resources: List[Resource] = [] - valid_permissions_str = ", ".join(profile_permissions) + for index, dest in enumerate(self.Destination): + try: + destination = get_resource_reference(dest, resource_resolver, self.Source) + source = get_resource_reference(self.Source, resource_resolver, dest) + except ConnectorResourceError as e: + raise InvalidResourceException(self.logical_id, str(e)) - if not self.Permissions: - raise InvalidResourceException( - self.logical_id, - f"'Permissions' cannot be empty; valid values are: {valid_permissions_str}.", - ) + profile = get_profile(source.resource_type, destination.resource_type) - for permission in self.Permissions: - if permission not in profile_permissions: + if not profile: raise InvalidResourceException( self.logical_id, - f"Unsupported 'Permissions' provided; valid values are: {valid_permissions_str}.", + f"Unable to create connector from {source.resource_type} to {destination.resource_type}; it's not supported or the template is invalid.", ) - if valid_permissions_combinations: - sorted_permissions_combinations = [sorted(permission) for permission in valid_permissions_combinations] - if sorted(self.Permissions) not in sorted_permissions_combinations: - valid_permissions_combination_str = ", ".join( - " + ".join(permission) for permission in sorted_permissions_combinations - ) + # removing duplicate permissions + self.Permissions = list(set(self.Permissions)) + profile_type, profile_properties = profile["Type"], profile["Properties"] + profile_permissions = profile_properties["AccessCategories"] + valid_permissions_combinations = profile_properties.get("ValidAccessCategories") + + valid_permissions_str = ", ".join(profile_permissions) + if not self.Permissions: raise InvalidResourceException( self.logical_id, - f"Unsupported 'Permissions' provided; valid combinations are: {valid_permissions_combination_str}.", + f"'Permissions' cannot be empty; valid values are: {valid_permissions_str}.", ) - replacement = { - "Source.Arn": source.arn, - "Destination.Arn": destination.arn, - "Source.ResourceId": source.resource_id, - "Destination.ResourceId": destination.resource_id, - "Source.Name": source.name, - "Destination.Name": destination.name, - "Source.Qualifier": source.qualifier, - "Destination.Qualifier": destination.qualifier, - } - try: - profile_properties = profile_replace(profile_properties, replacement) - except ValueError as e: - raise InvalidResourceException(self.logical_id, str(e)) + for permission in self.Permissions: + if permission not in profile_permissions: + raise InvalidResourceException( + self.logical_id, + f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid values are: {valid_permissions_str}.", + ) - verify_profile_variables_replaced(profile_properties) + if valid_permissions_combinations: + sorted_permissions_combinations = [sorted(permission) for permission in valid_permissions_combinations] + if sorted(self.Permissions) not in sorted_permissions_combinations: + valid_permissions_combination_str = ", ".join( + " + ".join(permission) for permission in sorted_permissions_combinations + ) + raise InvalidResourceException( + self.logical_id, + f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid combinations are: {valid_permissions_combination_str}.", + ) - generated_resources: List[Resource] = [] - if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": - generated_resources.append( - self._construct_iam_policy(source, destination, profile_properties, resource_resolver) - ) - if profile_type == "AWS_SQS_QUEUE_POLICY": - generated_resources.append(self._construct_sqs_queue_policy(source, destination, profile_properties)) - if profile_type == "AWS_SNS_TOPIC_POLICY": - generated_resources.append(self._construct_sns_topic_policy(source, destination, profile_properties)) - if profile_type == "AWS_LAMBDA_PERMISSION": - generated_resources.extend( - self._construct_lambda_permission_policy(source, destination, profile_properties) - ) + replacement = { + "Source.Arn": source.arn, + "Destination.Arn": destination.arn, + "Source.ResourceId": source.resource_id, + "Destination.ResourceId": destination.resource_id, + "Source.Name": source.name, + "Destination.Name": destination.name, + "Source.Qualifier": source.qualifier, + "Destination.Qualifier": destination.qualifier, + } + + try: + profile_properties = profile_replace(profile_properties, replacement) + except ValueError as e: + raise InvalidResourceException(self.logical_id, str(e)) + + verify_profile_variables_replaced(profile_properties) + + if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": + generated_resources.append( + self._construct_iam_policy( + source, destination, profile_properties, resource_resolver, index, multi_dest + ) + ) + if profile_type == "AWS_SQS_QUEUE_POLICY": + generated_resources.append( + self._construct_sqs_queue_policy(source, destination, profile_properties, index, multi_dest) + ) + if profile_type == "AWS_SNS_TOPIC_POLICY": + generated_resources.append( + self._construct_sns_topic_policy(source, destination, profile_properties, index, multi_dest) + ) + if profile_type == "AWS_LAMBDA_PERMISSION": + generated_resources.extend( + self._construct_lambda_permission_policy(source, destination, profile_properties, index, multi_dest) + ) + + self._add_connector_metadata(generated_resources, original_template, source, destination) generated_logical_ids = [resource.logical_id for resource in generated_resources] replace_depends_on_logical_id(self.logical_id, generated_logical_ids, resource_resolver) - self._add_connector_metadata(generated_resources, original_template, source, destination) if generated_resources: return generated_resources @@ -1888,6 +1905,8 @@ def _construct_iam_policy( destination: ConnectorResourceReference, profile: ConnectorProfile, resource_resolver: ResourceResolver, + index: int, + multi_dest: bool, ) -> IAMManagedPolicy: source_policy = profile["SourcePolicy"] resource = source if source_policy else destination @@ -1900,7 +1919,9 @@ def _construct_iam_policy( ) policy_document = self._get_policy_statements(profile) - policy = IAMManagedPolicy(f"{self.logical_id}Policy") + + policy_name = f"{self.logical_id}PolicyDestination{index}" if multi_dest else f"{self.logical_id}Policy" + policy = IAMManagedPolicy(policy_name) policy.PolicyDocument = policy_document policy.Roles = [role_name] @@ -1923,6 +1944,8 @@ def _construct_lambda_permission_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, + index: int, + multi_dest: bool, ) -> List[LambdaPermission]: source_policy = profile["SourcePolicy"] lambda_function = source if source_policy else destination @@ -1937,7 +1960,12 @@ def _construct_lambda_permission_policy( lambda_permissions = [] for name in profile["AccessCategories"].keys(): if name in self.Permissions: - permission = LambdaPermission(f"{self.logical_id}{name}LambdaPermission") + permission_name = ( + f"{self.logical_id}{name}LambdaPermissionDestination{index}" + if multi_dest + else f"{self.logical_id}{name}LambdaPermission" + ) + permission = LambdaPermission(permission_name) permissions = profile["AccessCategories"][name] permission.Action = permissions["Action"] permission.FunctionName = function_arn @@ -1953,6 +1981,8 @@ def _construct_sns_topic_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, + index: int, + multi_dest: bool, ) -> SNSTopicPolicy: source_policy = profile["SourcePolicy"] sns_topic = source if source_policy else destination @@ -1964,7 +1994,10 @@ def _construct_sns_topic_policy( self.logical_id, f"Unable to get SNS topic ARN from '{property_name}' resource." ) - topic_policy = SNSTopicPolicy(f"{self.logical_id}TopicPolicy") + topic_policy_name = ( + f"{self.logical_id}TopicPolicyDestination{index}" if multi_dest else f"{self.logical_id}TopicPolicy" + ) + topic_policy = SNSTopicPolicy(topic_policy_name) topic_policy.Topics = [topic_arn] topic_policy.PolicyDocument = self._get_policy_statements(profile) @@ -1975,6 +2008,8 @@ def _construct_sqs_queue_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, + index: int, + multi_dest: bool, ) -> SQSQueuePolicy: source_policy = profile["SourcePolicy"] sqs_queue = source if source_policy else destination @@ -1986,7 +2021,10 @@ def _construct_sqs_queue_policy( self.logical_id, f"Unable to get SQS queue URL from '{property_name}' resource." ) - queue_policy = SQSQueuePolicy(f"{self.logical_id}QueuePolicy") + queue_policy_name = ( + f"{self.logical_id}QueuePolicyDestination{index}" if multi_dest else f"{self.logical_id}QueuePolicy" + ) + queue_policy = SQSQueuePolicy(queue_policy_name) queue_policy.PolicyDocument = self._get_policy_statements(profile) queue_policy.Queues = [queue_url] diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index b388312c53..6553e5a93b 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -700,7 +700,7 @@ def test_unsupported_permissions_connector(self): connector.Destination = {"Id": "table"} connector.Permissions = ["INVOKE"] with self.assertRaisesRegex( - InvalidResourceException, ".+Unsupported 'Permissions' provided; valid values are: Read, Write." + InvalidResourceException, ".+Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::DynamoDB::Table; valid values are: Read, Write." ): connector.to_cloudformation(**self.kwargs)[0] @@ -710,7 +710,7 @@ def test_unsupported_permissions_connector_with_one_supported_permission(self): connector.Destination = {"Id": "func2"} connector.Permissions = ["INVOKE"] with self.assertRaisesRegex( - InvalidResourceException, ".+Unsupported 'Permissions' provided; valid values are: Read." + InvalidResourceException, ".+Unsupported 'Permissions' provided for connector from AWS::DynamoDB::Table to AWS::Lambda::Function; valid values are: Read." ): connector.to_cloudformation(**self.kwargs)[0] @@ -720,6 +720,6 @@ def test_unsupported_permissions_combination(self): connector.Destination = {"Id": "func2"} connector.Permissions = ["Read"] with self.assertRaisesRegex( - InvalidResourceException, "Unsupported 'Permissions' provided; valid combinations are: Read \\+ Write." + InvalidResourceException, "Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read \\+ Write." ): connector.to_cloudformation(**self.kwargs)[0] diff --git a/tests/translator/input/connector_api_to_function.yaml b/tests/translator/input/connector_api_to_function.yaml index a9ab7244d3..1ba7682762 100644 --- a/tests/translator/input/connector_api_to_function.yaml +++ b/tests/translator/input/connector_api_to_function.yaml @@ -83,7 +83,8 @@ Resources: Source: Id: MyApiGateway Destination: - Id: MyServerlessFunction + - Id: MyServerlessFunction + - Id: MyFunction Permissions: - Write diff --git a/tests/translator/output/error_connector.json b/tests/translator/output/error_connector.json index 4c6fd76ad1..49cfe266df 100644 --- a/tests/translator/output/error_connector.json +++ b/tests/translator/output/error_connector.json @@ -1,5 +1,5 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 16. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 16. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::SQS::Queue; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", "errors": [ { "errorMessage": "Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string." From e092f14ced58d1360207a877f7bbbba8554147eb Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Tue, 22 Nov 2022 18:05:36 -0800 Subject: [PATCH 02/13] Fix Metadata and add some transform test --- samtranslator/model/sam_resources.py | 11 +- tests/model/test_sam_resources.py | 9 +- .../input/connector_api_to_function.yaml | 3 +- .../connector_api_to_multiple_function.yaml | 52 ++++ .../connector_function_to_multiple_s3.yaml | 30 ++ .../input/connector_rule_to_multiple_sns.yaml | 42 +++ .../connector_api_to_multiple_function.json | 210 ++++++++++++++ .../connector_function_to_multiple_s3.json | 260 ++++++++++++++++++ .../connector_rule_to_multiple_sns.json | 155 +++++++++++ .../connector_api_to_multiple_function.json | 210 ++++++++++++++ .../connector_function_to_multiple_s3.json | 260 ++++++++++++++++++ .../connector_rule_to_multiple_sns.json | 155 +++++++++++ .../connector_api_to_multiple_function.json | 202 ++++++++++++++ .../connector_function_to_multiple_s3.json | 260 ++++++++++++++++++ .../connector_rule_to_multiple_sns.json | 155 +++++++++++ 15 files changed, 2005 insertions(+), 9 deletions(-) create mode 100644 tests/translator/input/connector_api_to_multiple_function.yaml create mode 100644 tests/translator/input/connector_function_to_multiple_s3.yaml create mode 100644 tests/translator/input/connector_rule_to_multiple_sns.yaml create mode 100644 tests/translator/output/aws-cn/connector_api_to_multiple_function.json create mode 100644 tests/translator/output/aws-cn/connector_function_to_multiple_s3.json create mode 100644 tests/translator/output/aws-cn/connector_rule_to_multiple_sns.json create mode 100644 tests/translator/output/aws-us-gov/connector_api_to_multiple_function.json create mode 100644 tests/translator/output/aws-us-gov/connector_function_to_multiple_s3.json create mode 100644 tests/translator/output/aws-us-gov/connector_rule_to_multiple_sns.json create mode 100644 tests/translator/output/connector_api_to_multiple_function.json create mode 100644 tests/translator/output/connector_function_to_multiple_s3.json create mode 100644 tests/translator/output/connector_rule_to_multiple_sns.json diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 4eeb9997cf..ea75199a46 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1792,7 +1792,7 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore multi_dest = False self.Destination = [self.Destination] - generated_resources: List[Resource] = [] + list_generated_resources: List[Resource] = [] for index, dest in enumerate(self.Destination): try: @@ -1858,6 +1858,8 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore verify_profile_variables_replaced(profile_properties) + generated_resources: List[Resource] = [] + if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": generated_resources.append( self._construct_iam_policy( @@ -1878,12 +1880,13 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore ) self._add_connector_metadata(generated_resources, original_template, source, destination) + list_generated_resources.extend(generated_resources) - generated_logical_ids = [resource.logical_id for resource in generated_resources] + generated_logical_ids = [resource.logical_id for resource in list_generated_resources] replace_depends_on_logical_id(self.logical_id, generated_logical_ids, resource_resolver) - if generated_resources: - return generated_resources + if list_generated_resources: + return list_generated_resources # Should support all profile types raise TypeError(f"Unknown profile policy type '{profile_type}'") diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 6553e5a93b..7b9163278a 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -700,7 +700,8 @@ def test_unsupported_permissions_connector(self): connector.Destination = {"Id": "table"} connector.Permissions = ["INVOKE"] with self.assertRaisesRegex( - InvalidResourceException, ".+Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::DynamoDB::Table; valid values are: Read, Write." + InvalidResourceException, + ".+Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::DynamoDB::Table; valid values are: Read, Write.", ): connector.to_cloudformation(**self.kwargs)[0] @@ -710,7 +711,8 @@ def test_unsupported_permissions_connector_with_one_supported_permission(self): connector.Destination = {"Id": "func2"} connector.Permissions = ["INVOKE"] with self.assertRaisesRegex( - InvalidResourceException, ".+Unsupported 'Permissions' provided for connector from AWS::DynamoDB::Table to AWS::Lambda::Function; valid values are: Read." + InvalidResourceException, + ".+Unsupported 'Permissions' provided for connector from AWS::DynamoDB::Table to AWS::Lambda::Function; valid values are: Read.", ): connector.to_cloudformation(**self.kwargs)[0] @@ -720,6 +722,7 @@ def test_unsupported_permissions_combination(self): connector.Destination = {"Id": "func2"} connector.Permissions = ["Read"] with self.assertRaisesRegex( - InvalidResourceException, "Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read \\+ Write." + InvalidResourceException, + "Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read \\+ Write.", ): connector.to_cloudformation(**self.kwargs)[0] diff --git a/tests/translator/input/connector_api_to_function.yaml b/tests/translator/input/connector_api_to_function.yaml index 1ba7682762..a9ab7244d3 100644 --- a/tests/translator/input/connector_api_to_function.yaml +++ b/tests/translator/input/connector_api_to_function.yaml @@ -83,8 +83,7 @@ Resources: Source: Id: MyApiGateway Destination: - - Id: MyServerlessFunction - - Id: MyFunction + Id: MyServerlessFunction Permissions: - Write diff --git a/tests/translator/input/connector_api_to_multiple_function.yaml b/tests/translator/input/connector_api_to_multiple_function.yaml new file mode 100644 index 0000000000..030f72321d --- /dev/null +++ b/tests/translator/input/connector_api_to_multiple_function.yaml @@ -0,0 +1,52 @@ +Resources: + MyServerlessApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + + MyServerlessFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + console.log(JSON.stringify(event)); + }; + + MyRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Action: sts:AssumeRole + Principal: + Service: lambda.amazonaws.com + ManagedPolicyArns: + - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + MyFunction: + Type: AWS::Lambda::Function + Properties: + Role: !GetAtt MyRole.Arn + Runtime: nodejs14.x + Handler: index.handler + Code: + ZipFile: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + console.log(JSON.stringify(event)); + }; + + MyConnectorServerlessApiToLambda: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: MyServerlessApi + Destination: + - Id: MyFunction + - Id: MyServerlessFunction + Permissions: + - Write diff --git a/tests/translator/input/connector_function_to_multiple_s3.yaml b/tests/translator/input/connector_function_to_multiple_s3.yaml new file mode 100644 index 0000000000..e101a30935 --- /dev/null +++ b/tests/translator/input/connector_function_to_multiple_s3.yaml @@ -0,0 +1,30 @@ +Resources: + MyFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + console.log(JSON.stringify(event)); + }; + + MyBucket: + Type: AWS::S3::Bucket + + MyBucket2: + Type: AWS::S3::Bucket + + MyConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: MyFunction + Destination: + - Id: MyBucket + - Type: AWS::S3::Bucket + Arn: !GetAtt MyBucket2.Arn + Permissions: + - Read + - Write diff --git a/tests/translator/input/connector_rule_to_multiple_sns.yaml b/tests/translator/input/connector_rule_to_multiple_sns.yaml new file mode 100644 index 0000000000..74be70d5d2 --- /dev/null +++ b/tests/translator/input/connector_rule_to_multiple_sns.yaml @@ -0,0 +1,42 @@ +Resources: + MyNewEventsRule: + Type: AWS::Events::Rule + Properties: + Name: mynewabc + EventPattern: + source: + - aws.ec2 + State: ENABLED + Targets: + - RoleArn: !GetAtt MyRuleRole.Arn + Arn: !GetAtt 'StateMachine.Arn' + Id: StateMachine + + MyRuleRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: !Sub events.amazonaws.com + Action: sts:AssumeRole + + MySNSTopic: + Type: AWS::SNS::Topic + DependsOn: MyConnector + + MySNSTopic2: + Type: AWS::SNS::Topic + + MyConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: MyNewEventsRule + Destination: + - Id: MySNSTopic + - Type: AWS::SNS::Topic + Arn: !Ref MySNSTopic2.Arn + Permissions: + - Write diff --git a/tests/translator/output/aws-cn/connector_api_to_multiple_function.json b/tests/translator/output/aws-cn/connector_api_to_multiple_function.json new file mode 100644 index 0000000000..28e7b88555 --- /dev/null +++ b/tests/translator/output/aws-cn/connector_api_to_multiple_function.json @@ -0,0 +1,210 @@ +{ + "Resources": { + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Lambda::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyServerlessFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "Type": "AWS::Lambda::Function" + }, + "MyRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyServerlessApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyServerlessApiDeployment5332c373d4": { + "Properties": { + "Description": "RestApi deployment id: 5332c373d45c69e6c0f562b4a419aa8eb311adc7", + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyServerlessApiProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyServerlessApiDeployment5332c373d4" + }, + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyServerlessFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyServerlessFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyServerlessFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-cn/connector_function_to_multiple_s3.json b/tests/translator/output/aws-cn/connector_function_to_multiple_s3.json new file mode 100644 index 0000000000..1a691e0044 --- /dev/null +++ b/tests/translator/output/aws-cn/connector_function_to_multiple_s3.json @@ -0,0 +1,260 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + }, + "MyBucket2": { + "Type": "AWS::S3::Bucket" + }, + "MyConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-cn/connector_rule_to_multiple_sns.json b/tests/translator/output/aws-cn/connector_rule_to_multiple_sns.json new file mode 100644 index 0000000000..e92d2c92d5 --- /dev/null +++ b/tests/translator/output/aws-cn/connector_rule_to_multiple_sns.json @@ -0,0 +1,155 @@ +{ + "Resources": { + "MyConnectorTopicPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyConnectorTopicPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic2.Arn" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic2.Arn" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyNewEventsRule": { + "Properties": { + "EventPattern": { + "source": [ + "aws.ec2" + ] + }, + "Name": "mynewabc", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "StateMachine", + "Arn" + ] + }, + "Id": "StateMachine", + "RoleArn": { + "Fn::GetAtt": [ + "MyRuleRole", + "Arn" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "MyRuleRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Sub": "events.amazonaws.com" + } + } + } + ] + } + }, + "Type": "AWS::IAM::Role" + }, + "MySNSTopic": { + "DependsOn": [ + "MyConnectorTopicPolicyDestination0", + "MyConnectorTopicPolicyDestination1" + ], + "Type": "AWS::SNS::Topic" + }, + "MySNSTopic2": { + "Type": "AWS::SNS::Topic" + } + } +} diff --git a/tests/translator/output/aws-us-gov/connector_api_to_multiple_function.json b/tests/translator/output/aws-us-gov/connector_api_to_multiple_function.json new file mode 100644 index 0000000000..2d69c2cf5a --- /dev/null +++ b/tests/translator/output/aws-us-gov/connector_api_to_multiple_function.json @@ -0,0 +1,210 @@ +{ + "Resources": { + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Lambda::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyServerlessFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "Type": "AWS::Lambda::Function" + }, + "MyRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyServerlessApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyServerlessApiDeployment5332c373d4": { + "Properties": { + "Description": "RestApi deployment id: 5332c373d45c69e6c0f562b4a419aa8eb311adc7", + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyServerlessApiProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyServerlessApiDeployment5332c373d4" + }, + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyServerlessFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyServerlessFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyServerlessFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/connector_function_to_multiple_s3.json b/tests/translator/output/aws-us-gov/connector_function_to_multiple_s3.json new file mode 100644 index 0000000000..31b4387bce --- /dev/null +++ b/tests/translator/output/aws-us-gov/connector_function_to_multiple_s3.json @@ -0,0 +1,260 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + }, + "MyBucket2": { + "Type": "AWS::S3::Bucket" + }, + "MyConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/connector_rule_to_multiple_sns.json b/tests/translator/output/aws-us-gov/connector_rule_to_multiple_sns.json new file mode 100644 index 0000000000..e92d2c92d5 --- /dev/null +++ b/tests/translator/output/aws-us-gov/connector_rule_to_multiple_sns.json @@ -0,0 +1,155 @@ +{ + "Resources": { + "MyConnectorTopicPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyConnectorTopicPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic2.Arn" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic2.Arn" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyNewEventsRule": { + "Properties": { + "EventPattern": { + "source": [ + "aws.ec2" + ] + }, + "Name": "mynewabc", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "StateMachine", + "Arn" + ] + }, + "Id": "StateMachine", + "RoleArn": { + "Fn::GetAtt": [ + "MyRuleRole", + "Arn" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "MyRuleRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Sub": "events.amazonaws.com" + } + } + } + ] + } + }, + "Type": "AWS::IAM::Role" + }, + "MySNSTopic": { + "DependsOn": [ + "MyConnectorTopicPolicyDestination0", + "MyConnectorTopicPolicyDestination1" + ], + "Type": "AWS::SNS::Topic" + }, + "MySNSTopic2": { + "Type": "AWS::SNS::Topic" + } + } +} diff --git a/tests/translator/output/connector_api_to_multiple_function.json b/tests/translator/output/connector_api_to_multiple_function.json new file mode 100644 index 0000000000..af99686066 --- /dev/null +++ b/tests/translator/output/connector_api_to_multiple_function.json @@ -0,0 +1,202 @@ +{ + "Resources": { + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Lambda::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyConnectorServerlessApiToLambdaWriteLambdaPermissionDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnectorServerlessApiToLambda": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Serverless::Api" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyServerlessFunction", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SourceResourceId}/${SourceQualifier}", + { + "SourceQualifier": "*", + "SourceResourceId": { + "Ref": "MyServerlessApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "Type": "AWS::Lambda::Function" + }, + "MyRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyServerlessApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyServerlessApiDeployment5332c373d4": { + "Properties": { + "Description": "RestApi deployment id: 5332c373d45c69e6c0f562b4a419aa8eb311adc7", + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyServerlessApiProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyServerlessApiDeployment5332c373d4" + }, + "RestApiId": { + "Ref": "MyServerlessApi" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyServerlessFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyServerlessFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyServerlessFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/connector_function_to_multiple_s3.json b/tests/translator/output/connector_function_to_multiple_s3.json new file mode 100644 index 0000000000..8368e58410 --- /dev/null +++ b/tests/translator/output/connector_function_to_multiple_s3.json @@ -0,0 +1,260 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + }, + "MyBucket2": { + "Type": "AWS::S3::Bucket" + }, + "MyConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "MyBucket2", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "MyFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n console.log(JSON.stringify(event));\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/connector_rule_to_multiple_sns.json b/tests/translator/output/connector_rule_to_multiple_sns.json new file mode 100644 index 0000000000..e92d2c92d5 --- /dev/null +++ b/tests/translator/output/connector_rule_to_multiple_sns.json @@ -0,0 +1,155 @@ +{ + "Resources": { + "MyConnectorTopicPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyConnectorTopicPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SNS::Topic" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyNewEventsRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Ref": "MySNSTopic2.Arn" + } + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "MySNSTopic2.Arn" + } + ] + }, + "Type": "AWS::SNS::TopicPolicy" + }, + "MyNewEventsRule": { + "Properties": { + "EventPattern": { + "source": [ + "aws.ec2" + ] + }, + "Name": "mynewabc", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "StateMachine", + "Arn" + ] + }, + "Id": "StateMachine", + "RoleArn": { + "Fn::GetAtt": [ + "MyRuleRole", + "Arn" + ] + } + } + ] + }, + "Type": "AWS::Events::Rule" + }, + "MyRuleRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Sub": "events.amazonaws.com" + } + } + } + ] + } + }, + "Type": "AWS::IAM::Role" + }, + "MySNSTopic": { + "DependsOn": [ + "MyConnectorTopicPolicyDestination0", + "MyConnectorTopicPolicyDestination1" + ], + "Type": "AWS::SNS::Topic" + }, + "MySNSTopic2": { + "Type": "AWS::SNS::Topic" + } + } +} From fbc9c76e99aa9dc5567d0cb0b63dc33378d3e61a Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Tue, 22 Nov 2022 18:08:57 -0800 Subject: [PATCH 03/13] Update schema --- samtranslator/schema/aws_serverless_connector.py | 4 ++-- samtranslator/schema/schema.json | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/samtranslator/schema/aws_serverless_connector.py b/samtranslator/schema/aws_serverless_connector.py index 11cd74fb27..0ad31abb74 100644 --- a/samtranslator/schema/aws_serverless_connector.py +++ b/samtranslator/schema/aws_serverless_connector.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Union from typing_extensions import Literal @@ -18,7 +18,7 @@ class ResourceReference(BaseModel): class Properties(BaseModel): Source: ResourceReference - Destination: ResourceReference + Destination: Union[ResourceReference, List[ResourceReference]] Permissions: List[Literal["Read", "Write"]] diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index 6f77de60c3..138f99c5eb 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -1285,7 +1285,18 @@ "$ref": "#/definitions/ResourceReference" }, "Destination": { - "$ref": "#/definitions/ResourceReference" + "title": "Destination", + "anyOf": [ + { + "$ref": "#/definitions/ResourceReference" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceReference" + } + } + ] }, "Permissions": { "title": "Permissions", From 359875f1fc69df9844bd7a59443cf9459ad9e805 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Wed, 23 Nov 2022 15:33:12 -0800 Subject: [PATCH 04/13] Address comments --- ...ctor_function_to_bucket_read_multiple.json | 192 ++++++++++++++++++ ...ctor_function_to_bucket_read_multiple.yaml | 57 ++++++ 2 files changed, 249 insertions(+) create mode 100644 integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json create mode 100644 integration/resources/templates/combination/connector_function_to_bucket_read_multiple.yaml diff --git a/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json b/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json new file mode 100644 index 0000000000..115fb8d733 --- /dev/null +++ b/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json @@ -0,0 +1,192 @@ +{ + "Metadata": { + "SamTransformTest": true + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket" + }, + "Bucket2": { + "Type": "AWS::S3::Bucket" + }, + "ConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Lambda::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Bucket", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "Bucket", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "LambdaRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "ConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::S3::Bucket" + }, + "Source": { + "Type": "AWS::Lambda::Function" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTorrent", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionTorrent", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Bucket2", + "Arn" + ] + }, + { + "Fn::Sub": [ + "${DestinationArn}/*", + { + "DestinationArn": { + "Fn::GetAtt": [ + "Bucket2", + "Arn" + ] + } + } + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "LambdaRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "LambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "TriggerFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nvar head_params = {\n Bucket: process.env.BUCKET,\n};\nvar head_params_2 = {\n Bucket: process.env.BUCKET2,\n}\nexports.handler = async (event) => {\n console.log('REQUEST RECEIVED:', JSON.stringify(event));\n var s3 = new AWS.S3();\n await s3.headBucket(head_params).promise();\n await s3.headBucket(head_params_2).promise();\n};\n" + }, + "Environment": { + "Variables": { + "BUCKET": { + "Ref": "Bucket" + }, + "BUCKET2": { + "Ref": "Bucket2" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "Type": "AWS::Lambda::Function" + } + } +} diff --git a/integration/resources/templates/combination/connector_function_to_bucket_read_multiple.yaml b/integration/resources/templates/combination/connector_function_to_bucket_read_multiple.yaml new file mode 100644 index 0000000000..57d68eb9e7 --- /dev/null +++ b/integration/resources/templates/combination/connector_function_to_bucket_read_multiple.yaml @@ -0,0 +1,57 @@ +Resources: + LambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Action: sts:AssumeRole + Principal: + Service: lambda.amazonaws.com + ManagedPolicyArns: + - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + TriggerFunction: + Type: AWS::Lambda::Function + Properties: + Role: !GetAtt LambdaRole.Arn + Runtime: nodejs14.x + Handler: index.handler + Code: + ZipFile: | + const AWS = require('aws-sdk'); + var head_params = { + Bucket: process.env.BUCKET, + }; + var head_params_2 = { + Bucket: process.env.BUCKET2, + } + exports.handler = async (event) => { + console.log('REQUEST RECEIVED:', JSON.stringify(event)); + var s3 = new AWS.S3(); + await s3.headBucket(head_params).promise(); + await s3.headBucket(head_params_2).promise(); + }; + Environment: + Variables: + BUCKET: !Ref Bucket + BUCKET2: !Ref Bucket2 + + Bucket: + Type: AWS::S3::Bucket + + Bucket2: + Type: AWS::S3::Bucket + + Connector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: TriggerFunction + Destination: + - Id: Bucket + - Id: Bucket2 + Permissions: + - Read +Metadata: + SamTransformTest: true From d1a2ec6ca9b411e8bf663aae219a857cc4c24401 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Wed, 23 Nov 2022 17:15:14 -0800 Subject: [PATCH 05/13] refactor code --- samtranslator/model/sam_resources.py | 193 +++++++++++++++------------ 1 file changed, 104 insertions(+), 89 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index ea75199a46..6fe5f3f0c0 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1782,105 +1782,120 @@ class SamConnector(SamResourceMacro): "Permissions": PropertyType(True, list_of(is_str())), } - @cw_timer - def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore - resource_resolver: ResourceResolver = kwargs["resource_resolver"] - original_template = kwargs["original_template"] + def generate_resource( + self, + source: ConnectorResourceReference, + destination: ConnectorResourceReference, + dest_index: int, + multi_dest: bool, + resource_resolver: ResourceResolver, + ) -> List[Resource]: + profile = get_profile(source.resource_type, destination.resource_type) + if not profile: + raise InvalidResourceException( + self.logical_id, + f"Unable to create connector from {source.resource_type} to {destination.resource_type}; it's not supported or the template is invalid.", + ) - multi_dest = True - if isinstance(self.Destination, dict): - multi_dest = False - self.Destination = [self.Destination] + profile_type, profile_properties = profile["Type"], profile["Properties"] - list_generated_resources: List[Resource] = [] + # removing duplicate permissions + self.Permissions = list(set(self.Permissions)) + profile_permissions = profile_properties["AccessCategories"] + valid_permissions_combinations = profile_properties.get("ValidAccessCategories") - for index, dest in enumerate(self.Destination): - try: - destination = get_resource_reference(dest, resource_resolver, self.Source) - source = get_resource_reference(self.Source, resource_resolver, dest) - except ConnectorResourceError as e: - raise InvalidResourceException(self.logical_id, str(e)) - - profile = get_profile(source.resource_type, destination.resource_type) + valid_permissions_str = ", ".join(profile_permissions) + if not self.Permissions: + raise InvalidResourceException( + self.logical_id, + f"'Permissions' cannot be empty; valid values are: {valid_permissions_str}.", + ) - if not profile: + for permission in self.Permissions: + if permission not in profile_permissions: raise InvalidResourceException( self.logical_id, - f"Unable to create connector from {source.resource_type} to {destination.resource_type}; it's not supported or the template is invalid.", + f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid values are: {valid_permissions_str}.", ) - # removing duplicate permissions - self.Permissions = list(set(self.Permissions)) - profile_type, profile_properties = profile["Type"], profile["Properties"] - profile_permissions = profile_properties["AccessCategories"] - valid_permissions_combinations = profile_properties.get("ValidAccessCategories") - - valid_permissions_str = ", ".join(profile_permissions) - if not self.Permissions: + if valid_permissions_combinations: + sorted_permissions_combinations = [sorted(permission) for permission in valid_permissions_combinations] + if sorted(self.Permissions) not in sorted_permissions_combinations: + valid_permissions_combination_str = ", ".join( + " + ".join(permission) for permission in sorted_permissions_combinations + ) raise InvalidResourceException( self.logical_id, - f"'Permissions' cannot be empty; valid values are: {valid_permissions_str}.", + f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid combinations are: {valid_permissions_combination_str}.", ) - for permission in self.Permissions: - if permission not in profile_permissions: - raise InvalidResourceException( - self.logical_id, - f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid values are: {valid_permissions_str}.", - ) - - if valid_permissions_combinations: - sorted_permissions_combinations = [sorted(permission) for permission in valid_permissions_combinations] - if sorted(self.Permissions) not in sorted_permissions_combinations: - valid_permissions_combination_str = ", ".join( - " + ".join(permission) for permission in sorted_permissions_combinations - ) - raise InvalidResourceException( - self.logical_id, - f"Unsupported 'Permissions' provided for connector from {source.resource_type} to {destination.resource_type}; valid combinations are: {valid_permissions_combination_str}.", - ) - - replacement = { - "Source.Arn": source.arn, - "Destination.Arn": destination.arn, - "Source.ResourceId": source.resource_id, - "Destination.ResourceId": destination.resource_id, - "Source.Name": source.name, - "Destination.Name": destination.name, - "Source.Qualifier": source.qualifier, - "Destination.Qualifier": destination.qualifier, - } + replacement = { + "Source.Arn": source.arn, + "Destination.Arn": destination.arn, + "Source.ResourceId": source.resource_id, + "Destination.ResourceId": destination.resource_id, + "Source.Name": source.name, + "Destination.Name": destination.name, + "Source.Qualifier": source.qualifier, + "Destination.Qualifier": destination.qualifier, + } - try: - profile_properties = profile_replace(profile_properties, replacement) - except ValueError as e: - raise InvalidResourceException(self.logical_id, str(e)) + try: + profile_properties = profile_replace(profile_properties, replacement) + except ValueError as e: + raise InvalidResourceException(self.logical_id, str(e)) - verify_profile_variables_replaced(profile_properties) + verify_profile_variables_replaced(profile_properties) - generated_resources: List[Resource] = [] + generated_resources: List[Resource] = [] - if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": - generated_resources.append( - self._construct_iam_policy( - source, destination, profile_properties, resource_resolver, index, multi_dest - ) - ) - if profile_type == "AWS_SQS_QUEUE_POLICY": - generated_resources.append( - self._construct_sqs_queue_policy(source, destination, profile_properties, index, multi_dest) + if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": + generated_resources.append( + self._construct_iam_policy( + source, destination, profile_properties, resource_resolver, dest_index, multi_dest ) - if profile_type == "AWS_SNS_TOPIC_POLICY": - generated_resources.append( - self._construct_sns_topic_policy(source, destination, profile_properties, index, multi_dest) - ) - if profile_type == "AWS_LAMBDA_PERMISSION": - generated_resources.extend( - self._construct_lambda_permission_policy(source, destination, profile_properties, index, multi_dest) + ) + elif profile_type == "AWS_SQS_QUEUE_POLICY": + generated_resources.append( + self._construct_sqs_queue_policy(source, destination, profile_properties, dest_index, multi_dest) + ) + elif profile_type == "AWS_SNS_TOPIC_POLICY": + generated_resources.append( + self._construct_sns_topic_policy(source, destination, profile_properties, dest_index, multi_dest) + ) + elif profile_type == "AWS_LAMBDA_PERMISSION": + generated_resources.extend( + self._construct_lambda_permission_policy( + source, destination, profile_properties, dest_index, multi_dest ) + ) + else: + raise InvalidResourceException(self.logical_id, f"Profile type {profile_type} is not supported") + return generated_resources + + @cw_timer + def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore + resource_resolver: ResourceResolver = kwargs["resource_resolver"] + original_template = kwargs["original_template"] + + multi_dest = True + if isinstance(self.Destination, dict): + multi_dest = False + self.Destination = [self.Destination] + + list_generated_resources: List[Resource] = [] + + for dest_index, dest in enumerate(self.Destination): + try: + destination = get_resource_reference(dest, resource_resolver, self.Source) + source = get_resource_reference(self.Source, resource_resolver, dest) + except ConnectorResourceError as e: + raise InvalidResourceException(self.logical_id, str(e)) + + generated_resource = self.generate_resource(source, destination, dest_index, multi_dest, resource_resolver) - self._add_connector_metadata(generated_resources, original_template, source, destination) - list_generated_resources.extend(generated_resources) + self._add_connector_metadata(generated_resource, original_template, source, destination) + list_generated_resources.extend(generated_resource) generated_logical_ids = [resource.logical_id for resource in list_generated_resources] replace_depends_on_logical_id(self.logical_id, generated_logical_ids, resource_resolver) @@ -1889,7 +1904,7 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore return list_generated_resources # Should support all profile types - raise TypeError(f"Unknown profile policy type '{profile_type}'") + raise TypeError("The destination is empty") def _get_policy_statements(self, profile: ConnectorProfile) -> Dict[str, Any]: policy_statements = [] @@ -1908,7 +1923,7 @@ def _construct_iam_policy( destination: ConnectorResourceReference, profile: ConnectorProfile, resource_resolver: ResourceResolver, - index: int, + dest_index: int, multi_dest: bool, ) -> IAMManagedPolicy: source_policy = profile["SourcePolicy"] @@ -1923,7 +1938,7 @@ def _construct_iam_policy( policy_document = self._get_policy_statements(profile) - policy_name = f"{self.logical_id}PolicyDestination{index}" if multi_dest else f"{self.logical_id}Policy" + policy_name = f"{self.logical_id}PolicyDestination{dest_index}" if multi_dest else f"{self.logical_id}Policy" policy = IAMManagedPolicy(policy_name) policy.PolicyDocument = policy_document policy.Roles = [role_name] @@ -1947,7 +1962,7 @@ def _construct_lambda_permission_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, - index: int, + dest_index: int, multi_dest: bool, ) -> List[LambdaPermission]: source_policy = profile["SourcePolicy"] @@ -1964,7 +1979,7 @@ def _construct_lambda_permission_policy( for name in profile["AccessCategories"].keys(): if name in self.Permissions: permission_name = ( - f"{self.logical_id}{name}LambdaPermissionDestination{index}" + f"{self.logical_id}{name}LambdaPermissionDestination{dest_index}" if multi_dest else f"{self.logical_id}{name}LambdaPermission" ) @@ -1984,7 +1999,7 @@ def _construct_sns_topic_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, - index: int, + dest_index: int, multi_dest: bool, ) -> SNSTopicPolicy: source_policy = profile["SourcePolicy"] @@ -1998,7 +2013,7 @@ def _construct_sns_topic_policy( ) topic_policy_name = ( - f"{self.logical_id}TopicPolicyDestination{index}" if multi_dest else f"{self.logical_id}TopicPolicy" + f"{self.logical_id}TopicPolicyDestination{dest_index}" if multi_dest else f"{self.logical_id}TopicPolicy" ) topic_policy = SNSTopicPolicy(topic_policy_name) topic_policy.Topics = [topic_arn] @@ -2011,7 +2026,7 @@ def _construct_sqs_queue_policy( source: ConnectorResourceReference, destination: ConnectorResourceReference, profile: ConnectorProfile, - index: int, + dest_index: int, multi_dest: bool, ) -> SQSQueuePolicy: source_policy = profile["SourcePolicy"] @@ -2025,7 +2040,7 @@ def _construct_sqs_queue_policy( ) queue_policy_name = ( - f"{self.logical_id}QueuePolicyDestination{index}" if multi_dest else f"{self.logical_id}QueuePolicy" + f"{self.logical_id}QueuePolicyDestination{dest_index}" if multi_dest else f"{self.logical_id}QueuePolicy" ) queue_policy = SQSQueuePolicy(queue_policy_name) queue_policy.PolicyDocument = self._get_policy_statements(profile) From 5919f4f56e5b56b540bdc3f35e2f394e14aff776 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 09:32:25 -0800 Subject: [PATCH 06/13] add integration tests --- ...r_event_rule_to_lambda_write_multiple.json | 42 ++++ ...ctor_function_to_bucket_read_multiple.json | 214 ++---------------- .../connector_mix_destination.json | 34 +++ ...r_event_rule_to_lambda_write_multiple.yaml | 112 +++++++++ .../connector_mix_destination.yaml | 90 ++++++++ 5 files changed, 302 insertions(+), 190 deletions(-) create mode 100644 integration/resources/expected/combination/connector_event_rule_to_lambda_write_multiple.json create mode 100644 integration/resources/expected/combination/connector_mix_destination.json create mode 100644 integration/resources/templates/combination/connector_event_rule_to_lambda_write_multiple.yaml create mode 100644 integration/resources/templates/combination/connector_mix_destination.yaml diff --git a/integration/resources/expected/combination/connector_event_rule_to_lambda_write_multiple.json b/integration/resources/expected/combination/connector_event_rule_to_lambda_write_multiple.json new file mode 100644 index 0000000000..7e50bf655a --- /dev/null +++ b/integration/resources/expected/combination/connector_event_rule_to_lambda_write_multiple.json @@ -0,0 +1,42 @@ +[ + { + "LogicalResourceId": "EventRule", + "ResourceType": "AWS::Events::Rule" + }, + { + "LogicalResourceId": "Function", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "FunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "Function2", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "Function2Role", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MyConnectorWriteLambdaPermissionDestination0", + "ResourceType": "AWS::Lambda::Permission" + }, + { + "LogicalResourceId": "MyConnectorWriteLambdaPermissionDestination1", + "ResourceType": "AWS::Lambda::Permission" + }, + { + "LogicalResourceId": "TriggerFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "TriggerFunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "VerificationQueue", + "ResourceType": "AWS::SQS::Queue" + } +] diff --git a/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json b/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json index 115fb8d733..a47f90e35d 100644 --- a/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json +++ b/integration/resources/expected/combination/connector_function_to_bucket_read_multiple.json @@ -1,192 +1,26 @@ -{ - "Metadata": { - "SamTransformTest": true +[ + { + "LogicalResourceId": "LambdaRole", + "ResourceType": "AWS::IAM::Role" }, - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "ConnectorPolicyDestination0": { - "Metadata": { - "aws:sam:connectors": { - "Connector": { - "Destination": { - "Type": "AWS::S3::Bucket" - }, - "Source": { - "Type": "AWS::Lambda::Function" - } - } - } - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject", - "s3:GetObjectAcl", - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectTorrent", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTorrent", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:ListBucketVersions", - "s3:ListMultipartUploadParts" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket", - "Arn" - ] - }, - { - "Fn::Sub": [ - "${DestinationArn}/*", - { - "DestinationArn": { - "Fn::GetAtt": [ - "Bucket", - "Arn" - ] - } - } - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "Roles": [ - { - "Ref": "LambdaRole" - } - ] - }, - "Type": "AWS::IAM::ManagedPolicy" - }, - "ConnectorPolicyDestination1": { - "Metadata": { - "aws:sam:connectors": { - "Connector": { - "Destination": { - "Type": "AWS::S3::Bucket" - }, - "Source": { - "Type": "AWS::Lambda::Function" - } - } - } - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject", - "s3:GetObjectAcl", - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectTorrent", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTorrent", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:ListBucketVersions", - "s3:ListMultipartUploadParts" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket2", - "Arn" - ] - }, - { - "Fn::Sub": [ - "${DestinationArn}/*", - { - "DestinationArn": { - "Fn::GetAtt": [ - "Bucket2", - "Arn" - ] - } - } - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "Roles": [ - { - "Ref": "LambdaRole" - } - ] - }, - "Type": "AWS::IAM::ManagedPolicy" - }, - "LambdaRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - } - ] - }, - "Type": "AWS::IAM::Role" - }, - "TriggerFunction": { - "Properties": { - "Code": { - "ZipFile": "const AWS = require('aws-sdk');\nvar head_params = {\n Bucket: process.env.BUCKET,\n};\nvar head_params_2 = {\n Bucket: process.env.BUCKET2,\n}\nexports.handler = async (event) => {\n console.log('REQUEST RECEIVED:', JSON.stringify(event));\n var s3 = new AWS.S3();\n await s3.headBucket(head_params).promise();\n await s3.headBucket(head_params_2).promise();\n};\n" - }, - "Environment": { - "Variables": { - "BUCKET": { - "Ref": "Bucket" - }, - "BUCKET2": { - "Ref": "Bucket2" - } - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "LambdaRole", - "Arn" - ] - }, - "Runtime": "nodejs14.x" - }, - "Type": "AWS::Lambda::Function" - } + { + "LogicalResourceId": "TriggerFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "Bucket", + "ResourceType": "AWS::S3::Bucket" + }, + { + "LogicalResourceId": "Bucket2", + "ResourceType": "AWS::S3::Bucket" + }, + { + "LogicalResourceId": "ConnectorPolicyDestination0", + "ResourceType": "AWS::IAM::ManagedPolicy" + }, + { + "LogicalResourceId": "ConnectorPolicyDestination1", + "ResourceType": "AWS::IAM::ManagedPolicy" } -} +] diff --git a/integration/resources/expected/combination/connector_mix_destination.json b/integration/resources/expected/combination/connector_mix_destination.json new file mode 100644 index 0000000000..1a6626a239 --- /dev/null +++ b/integration/resources/expected/combination/connector_mix_destination.json @@ -0,0 +1,34 @@ +[ + { + "LogicalResourceId": "EventRule", + "ResourceType": "AWS::Events::Rule" + }, + { + "LogicalResourceId": "Function", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "FunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MyConnectorWriteLambdaPermissionDestination0", + "ResourceType": "AWS::Lambda::Permission" + }, + { + "LogicalResourceId": "TriggerFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "TriggerFunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "VerificationQueue", + "ResourceType": "AWS::SQS::Queue" + }, + { + "LogicalResourceId": "MyConnectorQueuePolicyDestination1", + "ResourceType": "AWS::SQS::QueuePolicy" + } +] diff --git a/integration/resources/templates/combination/connector_event_rule_to_lambda_write_multiple.yaml b/integration/resources/templates/combination/connector_event_rule_to_lambda_write_multiple.yaml new file mode 100644 index 0000000000..e2e7ea16e9 --- /dev/null +++ b/integration/resources/templates/combination/connector_event_rule_to_lambda_write_multiple.yaml @@ -0,0 +1,112 @@ +Resources: + TriggerFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + Timeout: 10 # in case eb has delay + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const eb = new AWS.EventBridge(); + const response = await eb.putEvents({ + Entries: [{ + Source: process.env.EVENT_SOURCE, + Detail: "{}", + DetailType: "Test", + }] + }).promise(); + + const sqs = new AWS.SQS(); + const data = await sqs.receiveMessage({ + QueueUrl: process.env.QUEUE_URL, + WaitTimeSeconds: 5, + }).promise(); + + if (data.Messages.length == 0) { + throw 'No messages in the queue!'; + } + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + EVENT_SOURCE: !Sub '${AWS::StackName}-test-event' + Policies: + - EventBridgePutEventsPolicy: + EventBusName: default + - SQSPollerPolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + EventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub 'EventRule-${AWS::StackName}' + EventPattern: + source: + - !Sub '${AWS::StackName}-test-event' + Targets: + - Arn: !GetAtt Function.Arn + Id: Target + - Arn: !GetAtt Function2.Arn + Id: Target2 + + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + Function2: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + VerificationQueue: + Type: AWS::SQS::Queue + + MyConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: EventRule + Destination: + - Id: Function + - Id: Function2 + Permissions: + - Write +Metadata: + SamTransformTest: true diff --git a/integration/resources/templates/combination/connector_mix_destination.yaml b/integration/resources/templates/combination/connector_mix_destination.yaml new file mode 100644 index 0000000000..99f1925620 --- /dev/null +++ b/integration/resources/templates/combination/connector_mix_destination.yaml @@ -0,0 +1,90 @@ +Resources: + TriggerFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + Timeout: 10 # in case eb has delay + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const eb = new AWS.EventBridge(); + const response = await eb.putEvents({ + Entries: [{ + Source: process.env.EVENT_SOURCE, + Detail: "{}", + DetailType: "Test", + }] + }).promise(); + + const sqs = new AWS.SQS(); + const data = await sqs.getQueueAttributes({ + QueueUrl: process.env.QUEUE_URL, + AttributeNames: ['ApproximateNumberOfMessages'] + }).promise(); + + if (data.Attributes.ApproximateNumberOfMessages < 2) { + throw 'Not enough messages in the queue!'; + } + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + EVENT_SOURCE: !Sub '${AWS::StackName}-test-event' + Policies: + - EventBridgePutEventsPolicy: + EventBusName: default + - SQSPollerPolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + EventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub 'EventRule-${AWS::StackName}' + EventPattern: + source: + - !Sub '${AWS::StackName}-test-event' + Targets: + - Arn: !GetAtt Function.Arn + Id: Target + - Arn: !GetAtt VerificationQueue.Arn + Id: Target2 + + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + VerificationQueue: + Type: AWS::SQS::Queue + + MyConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: EventRule + Destination: + - Id: Function + - Id: VerificationQueue + Permissions: + - Write +Metadata: + SamTransformTest: true From b48a24063470616272029ede896a386509f7c868 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 10:16:33 -0800 Subject: [PATCH 07/13] add another test --- samtranslator/model/sam_resources.py | 1 - .../connector_sqs_to_multiple_function.yaml | 80 +++++ .../connector_sqs_to_multiple_function.json | 332 ++++++++++++++++++ .../connector_sqs_to_multiple_function.json | 332 ++++++++++++++++++ .../connector_sqs_to_multiple_function.json | 332 ++++++++++++++++++ 5 files changed, 1076 insertions(+), 1 deletion(-) create mode 100644 tests/translator/input/connector_sqs_to_multiple_function.yaml create mode 100644 tests/translator/output/aws-cn/connector_sqs_to_multiple_function.json create mode 100644 tests/translator/output/aws-us-gov/connector_sqs_to_multiple_function.json create mode 100644 tests/translator/output/connector_sqs_to_multiple_function.json diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 6fe5f3f0c0..ca891c54ec 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1903,7 +1903,6 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore if list_generated_resources: return list_generated_resources - # Should support all profile types raise TypeError("The destination is empty") def _get_policy_statements(self, profile: ConnectorProfile) -> Dict[str, Any]: diff --git a/tests/translator/input/connector_sqs_to_multiple_function.yaml b/tests/translator/input/connector_sqs_to_multiple_function.yaml new file mode 100644 index 0000000000..ff03cc7c4e --- /dev/null +++ b/tests/translator/input/connector_sqs_to_multiple_function.yaml @@ -0,0 +1,80 @@ +Resources: + Queue: + Type: AWS::SQS::Queue + + InvokedFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.VERIFICATION_QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + VERIFICATION_QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + InvokedFunction2: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.VERIFICATION_QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + VERIFICATION_QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + SQSEventSourceMapping: + Type: AWS::Lambda::EventSourceMapping + Properties: + FunctionName: !Ref InvokedFunction + EventSourceArn: + Fn::GetAtt: + - Queue + - Arn + BatchSize: 10 + + SQSEventSourceMapping2: + Type: AWS::Lambda::EventSourceMapping + Properties: + FunctionName: !Ref InvokedFunction2 + EventSourceArn: + Fn::GetAtt: + - Queue + - Arn + BatchSize: 10 + + VerificationQueue: + Type: AWS::SQS::Queue + + Connector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: Queue + Destination: + - Id: InvokedFunction + - Id: InvokedFunction2 + Permissions: + - Read + - Write diff --git a/tests/translator/output/aws-cn/connector_sqs_to_multiple_function.json b/tests/translator/output/aws-cn/connector_sqs_to_multiple_function.json new file mode 100644 index 0000000000..ce9c3ff6b5 --- /dev/null +++ b/tests/translator/output/aws-cn/connector_sqs_to_multiple_function.json @@ -0,0 +1,332 @@ +{ + "Resources": { + "ConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "ConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunction2Role" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "InvokedFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2Role": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunction2RolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "InvokedFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunctionRolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "Queue": { + "Type": "AWS::SQS::Queue" + }, + "SQSEventSourceMapping": { + "DependsOn": [ + "ConnectorPolicyDestination0" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "SQSEventSourceMapping2": { + "DependsOn": [ + "ConnectorPolicyDestination1" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction2" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + } + } +} diff --git a/tests/translator/output/aws-us-gov/connector_sqs_to_multiple_function.json b/tests/translator/output/aws-us-gov/connector_sqs_to_multiple_function.json new file mode 100644 index 0000000000..b4cd82c8e0 --- /dev/null +++ b/tests/translator/output/aws-us-gov/connector_sqs_to_multiple_function.json @@ -0,0 +1,332 @@ +{ + "Resources": { + "ConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "ConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunction2Role" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "InvokedFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2Role": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunction2RolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "InvokedFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunctionRolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "Queue": { + "Type": "AWS::SQS::Queue" + }, + "SQSEventSourceMapping": { + "DependsOn": [ + "ConnectorPolicyDestination0" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "SQSEventSourceMapping2": { + "DependsOn": [ + "ConnectorPolicyDestination1" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction2" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + } + } +} diff --git a/tests/translator/output/connector_sqs_to_multiple_function.json b/tests/translator/output/connector_sqs_to_multiple_function.json new file mode 100644 index 0000000000..9cfa86b870 --- /dev/null +++ b/tests/translator/output/connector_sqs_to_multiple_function.json @@ -0,0 +1,332 @@ +{ + "Resources": { + "ConnectorPolicyDestination0": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunctionRole" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "ConnectorPolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "Connector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:DeleteMessage" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { + "Ref": "InvokedFunction2Role" + } + ] + }, + "Type": "AWS::IAM::ManagedPolicy" + }, + "InvokedFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.VERIFICATION_QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "VERIFICATION_QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "InvokedFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InvokedFunction2Role": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunction2RolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "InvokedFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "InvokedFunctionRolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "Queue": { + "Type": "AWS::SQS::Queue" + }, + "SQSEventSourceMapping": { + "DependsOn": [ + "ConnectorPolicyDestination0" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "SQSEventSourceMapping2": { + "DependsOn": [ + "ConnectorPolicyDestination1" + ], + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "FunctionName": { + "Ref": "InvokedFunction2" + } + }, + "Type": "AWS::Lambda::EventSourceMapping" + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + } + } +} From 7199bed91a55053fe0e11e20d4552821796b8fe2 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 11:25:44 -0800 Subject: [PATCH 08/13] address commments and add more tests --- samtranslator/model/sam_resources.py | 17 +- .../input/connector_mix_destination.yaml | 90 +++++ .../aws-cn/connector_mix_destination.json | 328 ++++++++++++++++++ .../aws-us-gov/connector_mix_destination.json | 328 ++++++++++++++++++ .../output/connector_mix_destination.json | 328 ++++++++++++++++++ 5 files changed, 1081 insertions(+), 10 deletions(-) create mode 100644 tests/translator/input/connector_mix_destination.yaml create mode 100644 tests/translator/output/aws-cn/connector_mix_destination.json create mode 100644 tests/translator/output/aws-us-gov/connector_mix_destination.json create mode 100644 tests/translator/output/connector_mix_destination.json diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index ca891c54ec..636be3495b 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1782,7 +1782,7 @@ class SamConnector(SamResourceMacro): "Permissions": PropertyType(True, list_of(is_str())), } - def generate_resource( + def generate_resources( self, source: ConnectorResourceReference, destination: ConnectorResourceReference, @@ -1797,10 +1797,9 @@ def generate_resource( f"Unable to create connector from {source.resource_type} to {destination.resource_type}; it's not supported or the template is invalid.", ) - profile_type, profile_properties = profile["Type"], profile["Properties"] - # removing duplicate permissions self.Permissions = list(set(self.Permissions)) + profile_type, profile_properties = profile["Type"], profile["Properties"] profile_permissions = profile_properties["AccessCategories"] valid_permissions_combinations = profile_properties.get("ValidAccessCategories") @@ -1839,7 +1838,6 @@ def generate_resource( "Source.Qualifier": source.qualifier, "Destination.Qualifier": destination.qualifier, } - try: profile_properties = profile_replace(profile_properties, replacement) except ValueError as e: @@ -1848,7 +1846,6 @@ def generate_resource( verify_profile_variables_replaced(profile_properties) generated_resources: List[Resource] = [] - if profile_type == "AWS_IAM_ROLE_MANAGED_POLICY": generated_resources.append( self._construct_iam_policy( @@ -1870,7 +1867,7 @@ def generate_resource( ) ) else: - raise InvalidResourceException(self.logical_id, f"Profile type {profile_type} is not supported") + raise TypeError(f"Profile type {profile_type} is not supported") return generated_resources @cw_timer @@ -1892,10 +1889,10 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore except ConnectorResourceError as e: raise InvalidResourceException(self.logical_id, str(e)) - generated_resource = self.generate_resource(source, destination, dest_index, multi_dest, resource_resolver) + generated_resources = self.generate_resources(source, destination, dest_index, multi_dest, resource_resolver) - self._add_connector_metadata(generated_resource, original_template, source, destination) - list_generated_resources.extend(generated_resource) + self._add_connector_metadata(generated_resources, original_template, source, destination) + list_generated_resources.extend(generated_resources) generated_logical_ids = [resource.logical_id for resource in list_generated_resources] replace_depends_on_logical_id(self.logical_id, generated_logical_ids, resource_resolver) @@ -1903,7 +1900,7 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore if list_generated_resources: return list_generated_resources - raise TypeError("The destination is empty") + raise TypeError(f"The connector {self.logical_id} doesn't generate any resources") def _get_policy_statements(self, profile: ConnectorProfile) -> Dict[str, Any]: policy_statements = [] diff --git a/tests/translator/input/connector_mix_destination.yaml b/tests/translator/input/connector_mix_destination.yaml new file mode 100644 index 0000000000..99f1925620 --- /dev/null +++ b/tests/translator/input/connector_mix_destination.yaml @@ -0,0 +1,90 @@ +Resources: + TriggerFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + Timeout: 10 # in case eb has delay + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const eb = new AWS.EventBridge(); + const response = await eb.putEvents({ + Entries: [{ + Source: process.env.EVENT_SOURCE, + Detail: "{}", + DetailType: "Test", + }] + }).promise(); + + const sqs = new AWS.SQS(); + const data = await sqs.getQueueAttributes({ + QueueUrl: process.env.QUEUE_URL, + AttributeNames: ['ApproximateNumberOfMessages'] + }).promise(); + + if (data.Attributes.ApproximateNumberOfMessages < 2) { + throw 'Not enough messages in the queue!'; + } + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + EVENT_SOURCE: !Sub '${AWS::StackName}-test-event' + Policies: + - EventBridgePutEventsPolicy: + EventBusName: default + - SQSPollerPolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + EventRule: + Type: AWS::Events::Rule + Properties: + Description: !Sub 'EventRule-${AWS::StackName}' + EventPattern: + source: + - !Sub '${AWS::StackName}-test-event' + Targets: + - Arn: !GetAtt Function.Arn + Id: Target + - Arn: !GetAtt VerificationQueue.Arn + Id: Target2 + + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + InlineCode: | + const AWS = require('aws-sdk'); + + exports.handler = async (event) => { + const sqs = new AWS.SQS(); + await sqs.sendMessage({ + QueueUrl: process.env.QUEUE_URL, + MessageBody: "test" + }).promise(); + }; + Environment: + Variables: + QUEUE_URL: !Ref VerificationQueue + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt VerificationQueue.QueueName + + VerificationQueue: + Type: AWS::SQS::Queue + + MyConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: EventRule + Destination: + - Id: Function + - Id: VerificationQueue + Permissions: + - Write +Metadata: + SamTransformTest: true diff --git a/tests/translator/output/aws-cn/connector_mix_destination.json b/tests/translator/output/aws-cn/connector_mix_destination.json new file mode 100644 index 0000000000..ab08373e21 --- /dev/null +++ b/tests/translator/output/aws-cn/connector_mix_destination.json @@ -0,0 +1,328 @@ +{ + "Resources": { + "EventRule": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": { + "Fn::Sub": "EventRule-${AWS::StackName}" + }, + "EventPattern": { + "source": [ + { + "Fn::Sub": "${AWS::StackName}-test-event" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Id": "Target" + }, + { + "Arn": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Id": "Target2" + } + ] + } + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + }, + "TriggerFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TriggerFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Timeout": 10, + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + }, + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "TriggerFunctionRole": { + "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": "TriggerFunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + } + }, + { + "PolicyName": "TriggerFunctionRolePolicy1", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionRole": { + "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": "FunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:SendMessage*" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyConnectorWriteLambdaPermissionDestination0": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "MyConnectorQueuePolicyDestination1": { + "Type": "AWS::SQS::QueuePolicy", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + } + } + ] + }, + "Queues": [ + { + "Ref": "VerificationQueue" + } + ] + } + } + }, + "Metadata": { + "SamTransformTest": true + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/connector_mix_destination.json b/tests/translator/output/aws-us-gov/connector_mix_destination.json new file mode 100644 index 0000000000..b97e55c2db --- /dev/null +++ b/tests/translator/output/aws-us-gov/connector_mix_destination.json @@ -0,0 +1,328 @@ +{ + "Resources": { + "EventRule": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": { + "Fn::Sub": "EventRule-${AWS::StackName}" + }, + "EventPattern": { + "source": [ + { + "Fn::Sub": "${AWS::StackName}-test-event" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Id": "Target" + }, + { + "Arn": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Id": "Target2" + } + ] + } + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + }, + "TriggerFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TriggerFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Timeout": 10, + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + }, + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "TriggerFunctionRole": { + "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": "TriggerFunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + } + }, + { + "PolicyName": "TriggerFunctionRolePolicy1", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionRole": { + "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": "FunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:SendMessage*" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyConnectorWriteLambdaPermissionDestination0": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "MyConnectorQueuePolicyDestination1": { + "Type": "AWS::SQS::QueuePolicy", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + } + } + ] + }, + "Queues": [ + { + "Ref": "VerificationQueue" + } + ] + } + } + }, + "Metadata": { + "SamTransformTest": true + } +} \ No newline at end of file diff --git a/tests/translator/output/connector_mix_destination.json b/tests/translator/output/connector_mix_destination.json new file mode 100644 index 0000000000..6898ade0fb --- /dev/null +++ b/tests/translator/output/connector_mix_destination.json @@ -0,0 +1,328 @@ +{ + "Resources": { + "EventRule": { + "Type": "AWS::Events::Rule", + "Properties": { + "Description": { + "Fn::Sub": "EventRule-${AWS::StackName}" + }, + "EventPattern": { + "source": [ + { + "Fn::Sub": "${AWS::StackName}-test-event" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Id": "Target" + }, + { + "Arn": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Id": "Target2" + } + ] + } + }, + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + }, + "TriggerFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TriggerFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Timeout": 10, + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + }, + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "TriggerFunctionRole": { + "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": "TriggerFunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + } + }, + { + "PolicyName": "TriggerFunctionRolePolicy1", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionRole": { + "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": "FunctionRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sqs:SendMessage*" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyConnectorWriteLambdaPermissionDestination0": { + "Type": "AWS::Lambda::Permission", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::Serverless::Function" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "MyConnectorQueuePolicyDestination1": { + "Type": "AWS::SQS::QueuePolicy", + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Source": { + "Type": "AWS::Events::Rule" + }, + "Destination": { + "Type": "AWS::SQS::Queue" + } + } + } + }, + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + } + } + ] + }, + "Queues": [ + { + "Ref": "VerificationQueue" + } + ] + } + } + }, + "Metadata": { + "SamTransformTest": true + } +} \ No newline at end of file From d610e5f99de1eaba21c77ee58d28def7e2df9c21 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 11:33:19 -0800 Subject: [PATCH 09/13] make black/pr/schema --- samtranslator/model/sam_resources.py | 4 +- samtranslator/schema/schema.json | 2 + .../aws-cn/connector_mix_destination.json | 310 ++++----- .../aws-us-gov/connector_mix_destination.json | 310 ++++----- .../output/connector_mix_destination.json | 616 +++++++++--------- 5 files changed, 623 insertions(+), 619 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 636be3495b..fc8a9929a2 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1889,7 +1889,9 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore except ConnectorResourceError as e: raise InvalidResourceException(self.logical_id, str(e)) - generated_resources = self.generate_resources(source, destination, dest_index, multi_dest, resource_resolver) + generated_resources = self.generate_resources( + source, destination, dest_index, multi_dest, resource_resolver + ) self._add_connector_metadata(generated_resources, original_template, source, destination) list_generated_resources.extend(generated_resources) diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index 7878ae37f7..eb766ca239 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -1713,6 +1713,8 @@ }, "Destination": { "title": "Destination", + "description": "The destination resource\\. \n*Type*: [ResourceReference](sam-property-connector-resourcereference.md) \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", + "markdownDescription": "The destination resource\\. \n*Type*: [ResourceReference](sam-property-connector-resourcereference.md) \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "anyOf": [ { "$ref": "#/definitions/ResourceReference" diff --git a/tests/translator/output/aws-cn/connector_mix_destination.json b/tests/translator/output/aws-cn/connector_mix_destination.json index ab08373e21..7bb7e77a84 100644 --- a/tests/translator/output/aws-cn/connector_mix_destination.json +++ b/tests/translator/output/aws-cn/connector_mix_destination.json @@ -1,7 +1,9 @@ { + "Metadata": { + "SamTransformTest": true + }, "Resources": { "EventRule": { - "Type": "AWS::Events::Rule", "Properties": { "Description": { "Fn::Sub": "EventRule-${AWS::StackName}" @@ -33,49 +35,41 @@ "Id": "Target2" } ] - } - }, - "VerificationQueue": { - "Type": "AWS::SQS::Queue" + }, + "Type": "AWS::Events::Rule" }, - "TriggerFunction": { - "Type": "AWS::Lambda::Function", + "Function": { "Properties": { "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "TriggerFunctionRole", - "Arn" - ] + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" }, - "Runtime": "nodejs14.x", - "Timeout": 10, "Environment": { "Variables": { "QUEUE_URL": { "Ref": "VerificationQueue" - }, - "EVENT_SOURCE": { - "Fn::Sub": "${AWS::StackName}-test-event" } } }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", "Tags": [ { "Key": "lambda:createdBy", "Value": "SAM" } ] - } + }, + "Type": "AWS::Lambda::Function" }, - "TriggerFunctionRole": { - "Type": "AWS::IAM::Role", + "FunctionRole": { "Properties": { "AssumeRolePolicyDocument": { - "Version": "2012-10-17", "Statement": [ { "Action": [ @@ -88,45 +82,21 @@ ] } } - ] + ], + "Version": "2012-10-17" }, "ManagedPolicyArns": [ "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], "Policies": [ { - "PolicyName": "TriggerFunctionRolePolicy0", "PolicyDocument": { "Statement": [ { - "Effect": "Allow", - "Action": "events:PutEvents", - "Resource": { - "Fn::Sub": [ - "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", - { - "eventBusName": "default" - } - ] - } - } - ] - } - }, - { - "PolicyName": "TriggerFunctionRolePolicy1", - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", "Action": [ - "sqs:ChangeMessageVisibility", - "sqs:ChangeMessageVisibilityBatch", - "sqs:DeleteMessage", - "sqs:DeleteMessageBatch", - "sqs:GetQueueAttributes", - "sqs:ReceiveMessage" + "sqs:SendMessage*" ], + "Effect": "Allow", "Resource": { "Fn::Sub": [ "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", @@ -142,7 +112,8 @@ } } ] - } + }, + "PolicyName": "FunctionRolePolicy0" } ], "Tags": [ @@ -151,42 +122,126 @@ "Value": "SAM" } ] - } + }, + "Type": "AWS::IAM::Role" }, - "Function": { - "Type": "AWS::Lambda::Function", + "MyConnectorQueuePolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SQS::Queue" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, "Properties": { - "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" }, - "Handler": "index.handler", - "Role": { + "Queues": [ + { + "Ref": "VerificationQueue" + } + ] + }, + "Type": "AWS::SQS::QueuePolicy" + }, + "MyConnectorWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { "Fn::GetAtt": [ - "FunctionRole", + "Function", "Arn" ] }, - "Runtime": "nodejs14.x", + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "TriggerFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, "Environment": { "Variables": { + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + }, "QUEUE_URL": { "Ref": "VerificationQueue" } } }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TriggerFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", "Tags": [ { "Key": "lambda:createdBy", "Value": "SAM" } - ] - } + ], + "Timeout": 10 + }, + "Type": "AWS::Lambda::Function" }, - "FunctionRole": { - "Type": "AWS::IAM::Role", + "TriggerFunctionRole": { "Properties": { "AssumeRolePolicyDocument": { - "Version": "2012-10-17", "Statement": [ { "Action": [ @@ -199,21 +254,45 @@ ] } } - ] + ], + "Version": "2012-10-17" }, "ManagedPolicyArns": [ "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], "Policies": [ { - "PolicyName": "FunctionRolePolicy0", "PolicyDocument": { "Statement": [ { + "Action": "events:PutEvents", "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + }, + "PolicyName": "TriggerFunctionRolePolicy0" + }, + { + "PolicyDocument": { + "Statement": [ + { "Action": [ - "sqs:SendMessage*" + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" ], + "Effect": "Allow", "Resource": { "Fn::Sub": [ "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", @@ -229,7 +308,8 @@ } } ] - } + }, + "PolicyName": "TriggerFunctionRolePolicy1" } ], "Tags": [ @@ -238,91 +318,11 @@ "Value": "SAM" } ] - } - }, - "MyConnectorWriteLambdaPermissionDestination0": { - "Type": "AWS::Lambda::Permission", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" - }, - "Destination": { - "Type": "AWS::Serverless::Function" - } - } - } }, - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "EventRule", - "Arn" - ] - } - } + "Type": "AWS::IAM::Role" }, - "MyConnectorQueuePolicyDestination1": { - "Type": "AWS::SQS::QueuePolicy", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" - }, - "Destination": { - "Type": "AWS::SQS::Queue" - } - } - } - }, - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com" - }, - "Resource": { - "Fn::GetAtt": [ - "VerificationQueue", - "Arn" - ] - }, - "Action": "sqs:SendMessage", - "Condition": { - "ArnEquals": { - "aws:SourceArn": { - "Fn::GetAtt": [ - "EventRule", - "Arn" - ] - } - } - } - } - ] - }, - "Queues": [ - { - "Ref": "VerificationQueue" - } - ] - } + "VerificationQueue": { + "Type": "AWS::SQS::Queue" } - }, - "Metadata": { - "SamTransformTest": true } -} \ No newline at end of file +} diff --git a/tests/translator/output/aws-us-gov/connector_mix_destination.json b/tests/translator/output/aws-us-gov/connector_mix_destination.json index b97e55c2db..480013937f 100644 --- a/tests/translator/output/aws-us-gov/connector_mix_destination.json +++ b/tests/translator/output/aws-us-gov/connector_mix_destination.json @@ -1,7 +1,9 @@ { + "Metadata": { + "SamTransformTest": true + }, "Resources": { "EventRule": { - "Type": "AWS::Events::Rule", "Properties": { "Description": { "Fn::Sub": "EventRule-${AWS::StackName}" @@ -33,49 +35,41 @@ "Id": "Target2" } ] - } - }, - "VerificationQueue": { - "Type": "AWS::SQS::Queue" + }, + "Type": "AWS::Events::Rule" }, - "TriggerFunction": { - "Type": "AWS::Lambda::Function", + "Function": { "Properties": { "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "TriggerFunctionRole", - "Arn" - ] + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" }, - "Runtime": "nodejs14.x", - "Timeout": 10, "Environment": { "Variables": { "QUEUE_URL": { "Ref": "VerificationQueue" - }, - "EVENT_SOURCE": { - "Fn::Sub": "${AWS::StackName}-test-event" } } }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", "Tags": [ { "Key": "lambda:createdBy", "Value": "SAM" } ] - } + }, + "Type": "AWS::Lambda::Function" }, - "TriggerFunctionRole": { - "Type": "AWS::IAM::Role", + "FunctionRole": { "Properties": { "AssumeRolePolicyDocument": { - "Version": "2012-10-17", "Statement": [ { "Action": [ @@ -88,45 +82,21 @@ ] } } - ] + ], + "Version": "2012-10-17" }, "ManagedPolicyArns": [ "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], "Policies": [ { - "PolicyName": "TriggerFunctionRolePolicy0", "PolicyDocument": { "Statement": [ { - "Effect": "Allow", - "Action": "events:PutEvents", - "Resource": { - "Fn::Sub": [ - "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", - { - "eventBusName": "default" - } - ] - } - } - ] - } - }, - { - "PolicyName": "TriggerFunctionRolePolicy1", - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", "Action": [ - "sqs:ChangeMessageVisibility", - "sqs:ChangeMessageVisibilityBatch", - "sqs:DeleteMessage", - "sqs:DeleteMessageBatch", - "sqs:GetQueueAttributes", - "sqs:ReceiveMessage" + "sqs:SendMessage*" ], + "Effect": "Allow", "Resource": { "Fn::Sub": [ "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", @@ -142,7 +112,8 @@ } } ] - } + }, + "PolicyName": "FunctionRolePolicy0" } ], "Tags": [ @@ -151,42 +122,126 @@ "Value": "SAM" } ] - } + }, + "Type": "AWS::IAM::Role" }, - "Function": { - "Type": "AWS::Lambda::Function", + "MyConnectorQueuePolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SQS::Queue" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, "Properties": { - "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" }, - "Handler": "index.handler", - "Role": { + "Queues": [ + { + "Ref": "VerificationQueue" + } + ] + }, + "Type": "AWS::SQS::QueuePolicy" + }, + "MyConnectorWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Events::Rule" + } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { "Fn::GetAtt": [ - "FunctionRole", + "Function", "Arn" ] }, - "Runtime": "nodejs14.x", + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "TriggerFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, "Environment": { "Variables": { + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + }, "QUEUE_URL": { "Ref": "VerificationQueue" } } }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TriggerFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", "Tags": [ { "Key": "lambda:createdBy", "Value": "SAM" } - ] - } + ], + "Timeout": 10 + }, + "Type": "AWS::Lambda::Function" }, - "FunctionRole": { - "Type": "AWS::IAM::Role", + "TriggerFunctionRole": { "Properties": { "AssumeRolePolicyDocument": { - "Version": "2012-10-17", "Statement": [ { "Action": [ @@ -199,21 +254,45 @@ ] } } - ] + ], + "Version": "2012-10-17" }, "ManagedPolicyArns": [ "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], "Policies": [ { - "PolicyName": "FunctionRolePolicy0", "PolicyDocument": { "Statement": [ { + "Action": "events:PutEvents", "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + }, + "PolicyName": "TriggerFunctionRolePolicy0" + }, + { + "PolicyDocument": { + "Statement": [ + { "Action": [ - "sqs:SendMessage*" + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" ], + "Effect": "Allow", "Resource": { "Fn::Sub": [ "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", @@ -229,7 +308,8 @@ } } ] - } + }, + "PolicyName": "TriggerFunctionRolePolicy1" } ], "Tags": [ @@ -238,91 +318,11 @@ "Value": "SAM" } ] - } - }, - "MyConnectorWriteLambdaPermissionDestination0": { - "Type": "AWS::Lambda::Permission", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" - }, - "Destination": { - "Type": "AWS::Serverless::Function" - } - } - } }, - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "EventRule", - "Arn" - ] - } - } + "Type": "AWS::IAM::Role" }, - "MyConnectorQueuePolicyDestination1": { - "Type": "AWS::SQS::QueuePolicy", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" - }, - "Destination": { - "Type": "AWS::SQS::Queue" - } - } - } - }, - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com" - }, - "Resource": { - "Fn::GetAtt": [ - "VerificationQueue", - "Arn" - ] - }, - "Action": "sqs:SendMessage", - "Condition": { - "ArnEquals": { - "aws:SourceArn": { - "Fn::GetAtt": [ - "EventRule", - "Arn" - ] - } - } - } - } - ] - }, - "Queues": [ - { - "Ref": "VerificationQueue" - } - ] - } + "VerificationQueue": { + "Type": "AWS::SQS::Queue" } - }, - "Metadata": { - "SamTransformTest": true } -} \ No newline at end of file +} diff --git a/tests/translator/output/connector_mix_destination.json b/tests/translator/output/connector_mix_destination.json index 6898ade0fb..4fe0d40daa 100644 --- a/tests/translator/output/connector_mix_destination.json +++ b/tests/translator/output/connector_mix_destination.json @@ -1,328 +1,328 @@ { - "Resources": { - "EventRule": { - "Type": "AWS::Events::Rule", - "Properties": { - "Description": { - "Fn::Sub": "EventRule-${AWS::StackName}" - }, - "EventPattern": { - "source": [ - { - "Fn::Sub": "${AWS::StackName}-test-event" - } - ] - }, - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Id": "Target" - }, - { - "Arn": { - "Fn::GetAtt": [ - "VerificationQueue", - "Arn" - ] - }, - "Id": "Target2" - } - ] - } - }, - "VerificationQueue": { - "Type": "AWS::SQS::Queue" + "Metadata": { + "SamTransformTest": true }, - "TriggerFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "TriggerFunctionRole", - "Arn" - ] + "Resources": { + "EventRule": { + "Properties": { + "Description": { + "Fn::Sub": "EventRule-${AWS::StackName}" + }, + "EventPattern": { + "source": [ + { + "Fn::Sub": "${AWS::StackName}-test-event" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Id": "Target" + }, + { + "Arn": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + }, + "Id": "Target2" + } + ] + }, + "Type": "AWS::Events::Rule" }, - "Runtime": "nodejs14.x", - "Timeout": 10, - "Environment": { - "Variables": { - "QUEUE_URL": { - "Ref": "VerificationQueue" + "Function": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" + }, + "Environment": { + "Variables": { + "QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] }, - "EVENT_SOURCE": { - "Fn::Sub": "${AWS::StackName}-test-event" - } - } + "Type": "AWS::Lambda::Function" }, - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "TriggerFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" + "FunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "FunctionRolePolicy0" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } ] - } - } - ] + }, + "Type": "AWS::IAM::Role" }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Policies": [ - { - "PolicyName": "TriggerFunctionRolePolicy0", - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Action": "events:PutEvents", - "Resource": { - "Fn::Sub": [ - "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", - { - "eventBusName": "default" - } - ] - } - } - ] - } - }, - { - "PolicyName": "TriggerFunctionRolePolicy1", - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "sqs:ChangeMessageVisibility", - "sqs:ChangeMessageVisibilityBatch", - "sqs:DeleteMessage", - "sqs:DeleteMessageBatch", - "sqs:GetQueueAttributes", - "sqs:ReceiveMessage" - ], - "Resource": { - "Fn::Sub": [ - "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", - { - "queueName": { - "Fn::GetAtt": [ - "VerificationQueue", - "QueueName" - ] + "MyConnectorQueuePolicyDestination1": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::SQS::Queue" + }, + "Source": { + "Type": "AWS::Events::Rule" } - } - ] - } + } } - ] - } - } - ], - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const sqs = new AWS.SQS();\n await sqs.sendMessage({\n QueueUrl: process.env.QUEUE_URL,\n MessageBody: \"test\"\n }).promise();\n};\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs14.x", - "Environment": { - "Variables": { - "QUEUE_URL": { - "Ref": "VerificationQueue" - } - } - }, - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" + }, + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "VerificationQueue", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "VerificationQueue" + } ] - } - } - ] + }, + "Type": "AWS::SQS::QueuePolicy" }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Policies": [ - { - "PolicyName": "FunctionRolePolicy0", - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "sqs:SendMessage*" - ], - "Resource": { - "Fn::Sub": [ - "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", - { - "queueName": { - "Fn::GetAtt": [ - "VerificationQueue", - "QueueName" - ] + "MyConnectorWriteLambdaPermissionDestination0": { + "Metadata": { + "aws:sam:connectors": { + "MyConnector": { + "Destination": { + "Type": "AWS::Serverless::Function" + }, + "Source": { + "Type": "AWS::Events::Rule" } - } + } + } + }, + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "EventRule", + "Arn" ] - } } - ] - } - } - ], - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "MyConnectorWriteLambdaPermissionDestination0": { - "Type": "AWS::Lambda::Permission", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" }, - "Destination": { - "Type": "AWS::Serverless::Function" - } - } - } - }, - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] + "Type": "AWS::Lambda::Permission" }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "EventRule", - "Arn" - ] - } - } - }, - "MyConnectorQueuePolicyDestination1": { - "Type": "AWS::SQS::QueuePolicy", - "Metadata": { - "aws:sam:connectors": { - "MyConnector": { - "Source": { - "Type": "AWS::Events::Rule" - }, - "Destination": { - "Type": "AWS::SQS::Queue" - } - } - } - }, - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com" - }, - "Resource": { - "Fn::GetAtt": [ - "VerificationQueue", - "Arn" - ] - }, - "Action": "sqs:SendMessage", - "Condition": { - "ArnEquals": { - "aws:SourceArn": { + "TriggerFunction": { + "Properties": { + "Code": { + "ZipFile": "const AWS = require('aws-sdk');\n\nexports.handler = async (event) => {\n const eb = new AWS.EventBridge();\n const response = await eb.putEvents({\n Entries: [{\n Source: process.env.EVENT_SOURCE,\n Detail: \"{}\",\n DetailType: \"Test\",\n }]\n }).promise();\n\n const sqs = new AWS.SQS();\n const data = await sqs.getQueueAttributes({\n QueueUrl: process.env.QUEUE_URL,\n AttributeNames: ['ApproximateNumberOfMessages']\n }).promise();\n\n if (data.Attributes.ApproximateNumberOfMessages < 2) {\n throw 'Not enough messages in the queue!';\n }\n};\n" + }, + "Environment": { + "Variables": { + "EVENT_SOURCE": { + "Fn::Sub": "${AWS::StackName}-test-event" + }, + "QUEUE_URL": { + "Ref": "VerificationQueue" + } + } + }, + "Handler": "index.handler", + "Role": { "Fn::GetAtt": [ - "EventRule", - "Arn" + "TriggerFunctionRole", + "Arn" ] - } - } - } - } - ] + }, + "Runtime": "nodejs14.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Timeout": 10 + }, + "Type": "AWS::Lambda::Function" + }, + "TriggerFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "default" + } + ] + } + } + ] + }, + "PolicyName": "TriggerFunctionRolePolicy0" + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ChangeMessageVisibility", + "sqs:ChangeMessageVisibilityBatch", + "sqs:DeleteMessage", + "sqs:DeleteMessageBatch", + "sqs:GetQueueAttributes", + "sqs:ReceiveMessage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:${queueName}", + { + "queueName": { + "Fn::GetAtt": [ + "VerificationQueue", + "QueueName" + ] + } + } + ] + } + } + ] + }, + "PolicyName": "TriggerFunctionRolePolicy1" + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" }, - "Queues": [ - { - "Ref": "VerificationQueue" - } - ] - } + "VerificationQueue": { + "Type": "AWS::SQS::Queue" + } } - }, - "Metadata": { - "SamTransformTest": true - } -} \ No newline at end of file +} From 0bce1b972daf8be9a299644bbcec2545cbfff696 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 11:54:29 -0800 Subject: [PATCH 10/13] Add empty destination list test --- samtranslator/model/sam_resources.py | 2 +- tests/translator/input/error_connector.yaml | 10 ++++++++++ tests/translator/output/error_connector.json | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index fc8a9929a2..0486a82472 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1902,7 +1902,7 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore if list_generated_resources: return list_generated_resources - raise TypeError(f"The connector {self.logical_id} doesn't generate any resources") + raise InvalidResourceException(self.logical_id, "The destination is an empty list") def _get_policy_statements(self, profile: ConnectorProfile) -> Dict[str, Any]: policy_statements = [] diff --git a/tests/translator/input/error_connector.yaml b/tests/translator/input/error_connector.yaml index 3c7193c6c3..ff5921f545 100644 --- a/tests/translator/input/error_connector.yaml +++ b/tests/translator/input/error_connector.yaml @@ -181,3 +181,13 @@ Resources: Destination: Id: MyQueue Permissions: [] + + EmptyDestinationListConnector: + Type: AWS::Serverless::Connector + Properties: + Source: + Id: MyFunction + Destination: [] + Permissions: + - Read + - Write diff --git a/tests/translator/output/error_connector.json b/tests/translator/output/error_connector.json index 49cfe266df..4e3134f91d 100644 --- a/tests/translator/output/error_connector.json +++ b/tests/translator/output/error_connector.json @@ -1,5 +1,5 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 16. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::SQS::Queue; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 17. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyDestinationListConnector] is invalid. The destination is an empty list Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::SQS::Queue; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", "errors": [ { "errorMessage": "Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string." @@ -48,6 +48,9 @@ }, { "errorMessage": "Resource with id [EmptyListPermissionConnector] is invalid. property Permission not defined for resource of type AWS::Serverless::Connector" + }, + { + "errorMessage": "Resource with id [EmptyDestinationListConnector] is invalid. The destination is an empty list" } ] } From f7dcf2ffdbf1b0c0902f690fb56d73fc27ec6d1e Mon Sep 17 00:00:00 2001 From: Xia Zhao <78883180+xazhao@users.noreply.github.com> Date: Thu, 24 Nov 2022 12:19:14 -0800 Subject: [PATCH 11/13] Update samtranslator/model/sam_resources.py Co-authored-by: Chris Rehn <1280602+hoffa@users.noreply.github.com> --- samtranslator/model/sam_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 0486a82472..fdf20dfae1 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1902,7 +1902,7 @@ def to_cloudformation(self, **kwargs: Any) -> List[Resource]: # type: ignore if list_generated_resources: return list_generated_resources - raise InvalidResourceException(self.logical_id, "The destination is an empty list") + raise InvalidResourceException(self.logical_id, "'Destination' is an empty list") def _get_policy_statements(self, profile: ConnectorProfile) -> Dict[str, Any]: policy_statements = [] From e76c6d36fca0e596a03604953abb42c847d14853 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 13:28:52 -0800 Subject: [PATCH 12/13] enable integration tests --- integration/combination/test_connectors.py | 3 +++ .../templates/combination/connector_mix_destination.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/integration/combination/test_connectors.py b/integration/combination/test_connectors.py index 1efed2521d..c213f54d07 100644 --- a/integration/combination/test_connectors.py +++ b/integration/combination/test_connectors.py @@ -28,6 +28,7 @@ def tearDown(self): ("combination/connector_restapi_to_function",), ("combination/connector_httpapi_to_function",), ("combination/connector_function_to_bucket_read",), + ("combination/connector_function_to_bucket_read_multiple",), ("combination/connector_function_to_bucket_write",), ("combination/connector_function_to_table_read",), ("combination/connector_function_to_table_write",), @@ -44,6 +45,8 @@ def tearDown(self): ("combination/connector_event_rule_to_eb_default_write",), ("combination/connector_event_rule_to_eb_custom_write",), ("combination/connector_event_rule_to_lambda_write",), + ("combination/connector_event_rule_to_lambda_write_multiple",), + ("combination/connector_mix_destination",), ("combination/connector_sqs_to_function",), ("combination/connector_sns_to_function_write",), ("combination/connector_table_to_function_read",), diff --git a/integration/resources/templates/combination/connector_mix_destination.yaml b/integration/resources/templates/combination/connector_mix_destination.yaml index 99f1925620..ab372d83e0 100644 --- a/integration/resources/templates/combination/connector_mix_destination.yaml +++ b/integration/resources/templates/combination/connector_mix_destination.yaml @@ -17,7 +17,7 @@ Resources: DetailType: "Test", }] }).promise(); - + await new Promise(resolve => setTimeout(resolve, 5000)); const sqs = new AWS.SQS(); const data = await sqs.getQueueAttributes({ QueueUrl: process.env.QUEUE_URL, From 0b58d9346488507455927d722d1d4d6f09ad7215 Mon Sep 17 00:00:00 2001 From: Xia Zhao Date: Thu, 24 Nov 2022 13:39:17 -0800 Subject: [PATCH 13/13] update error message --- tests/translator/output/error_connector.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/translator/output/error_connector.json b/tests/translator/output/error_connector.json index 4e3134f91d..dd37014b52 100644 --- a/tests/translator/output/error_connector.json +++ b/tests/translator/output/error_connector.json @@ -1,5 +1,5 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 17. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyDestinationListConnector] is invalid. The destination is an empty list Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::SQS::Queue; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 17. Resource with id [BothIdAndOtherProps] is invalid. Must provide either 'Id' or a combination of the other properties, not both. Resource with id [EmptyDestinationListConnector] is invalid. 'Destination' is an empty list Resource with id [EmptyListPermissionConnector] is invalid. 'Permissions' cannot be empty; valid values are: Read, Write. Resource with id [EmptyPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [MissingLambdaFunctionArn] is invalid. Source.Arn is missing. Resource with id [MissingRole] is invalid. Unable to get IAM role name from 'Source' resource. Resource with id [MissingRoleDestination] is invalid. Unable to get IAM role name from 'Destination' resource. Resource with id [MissingSnsTopicArn] is invalid. Destination.Arn is missing. Resource with id [MissingSqsQueueUrl] is invalid. Destination.Arn is missing. Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string. Resource with id [NoPermissionConnector] is invalid. Missing required property 'Permissions'. Resource with id [NonExistentLogicalId] is invalid. Unable to find resource with logical ID 'ThisDoesntExist'. Resource with id [NonStrId] is invalid. 'Id' is missing or not a string. Resource with id [ResourceWithoutType] is invalid. 'Type' is missing or not a string. Resource with id [UnsupportedAccessCategory] is invalid. Unsupported 'Permissions' provided for connector from AWS::Lambda::Function to AWS::SQS::Queue; valid values are: Read, Write. Resource with id [UnsupportedAccessCategoryCombination] is invalid. Unsupported 'Permissions' provided for connector from AWS::SQS::Queue to AWS::Lambda::Function; valid combinations are: Read + Write. Resource with id [UnsupportedType] is invalid. Unable to create connector from AWS::Fancy::CoolType to AWS::Lambda::Function; it's not supported or the template is invalid.", "errors": [ { "errorMessage": "Resource with id [NoIdMissingType] is invalid. 'Type' is missing or not a string."