diff --git a/docs/cloudformation_compatibility.rst b/docs/cloudformation_compatibility.rst index 509d6d28e1..b431d9d27f 100644 --- a/docs/cloudformation_compatibility.rst +++ b/docs/cloudformation_compatibility.rst @@ -63,6 +63,7 @@ DeploymentPreference All Layers All AutoPublishAlias Ref of a CloudFormation Parameter Alias resources created by SAM uses a LocicalId . So SAM either needs a string for alias name, or a Ref to template Parameter that SAM can resolve into a string. ReservedConcurrentExecutions All +EventInvokeConfig All ============================ ================================== ======================== Events Properties diff --git a/docs/globals.rst b/docs/globals.rst index ada79c3523..54fd94df73 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -69,6 +69,7 @@ Currently, the following resources and properties are being supported: DeploymentPreference: PermissionsBoundary: ReservedConcurrentExecutions: + EventInvokeConfig: Api: # Properties of AWS::Serverless::Api diff --git a/examples/2016-10-31/function_lambda_event_destinations/README.md b/examples/2016-10-31/function_lambda_event_destinations/README.md new file mode 100644 index 0000000000..0d1f6c9e6a --- /dev/null +++ b/examples/2016-10-31/function_lambda_event_destinations/README.md @@ -0,0 +1,19 @@ +# API Gateway + Lambda REQUEST Authorizer Example + +This example shows you how to configure Event Invoke Config on a function. + +## Running the example + +Replace the Destination Arns for SQS, SNS, EventBridge with valid arns. +Deploy the example into your account: + +```bash +$ sam deploy \ + --template-file /path_to_template/packaged-template.yaml \ + --stack-name my-new-stack \ + --capabilities CAPABILITY_IAM +``` + +## Additional resources + +- https://aws.amazon.com/blogs/compute/introducing-aws-lambda-destinations/ diff --git a/examples/2016-10-31/function_lambda_event_destinations/src/index.js b/examples/2016-10-31/function_lambda_event_destinations/src/index.js new file mode 100644 index 0000000000..22c4654649 --- /dev/null +++ b/examples/2016-10-31/function_lambda_event_destinations/src/index.js @@ -0,0 +1,15 @@ +exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } +}; diff --git a/examples/2016-10-31/function_lambda_event_destinations/template.yaml b/examples/2016-10-31/function_lambda_event_destinations/template.yaml new file mode 100644 index 0000000000..a52144d309 --- /dev/null +++ b/examples/2016-10-31/function_lambda_event_destinations/template.yaml @@ -0,0 +1,83 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Lambda Event Destinations +Parameters: + SQSArn: + Type: String + Default: my-sqs-arn + UseExistingQueue: + Type: String + AllowedValues: + - true + - false + Default: true + CreateSNSTopic: + Type: String + AllowedValues: + - true + - false + Default: true +Conditions: + QueueCreationDisabled: !Equals [!Ref UseExistingQueue, true] + TopicCreationEnabled: !Equals [!Ref CreateSNSTopic, true] + +Resources: + EventDestinationLambda: + Type: AWS::Serverless::Function + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: Lambda + # If the type is Lambda/EventBridge, Destination property is required. + Destination: !GetAtt DestinationLambda.Arn + OnFailure: + # SQS and SNS will get auto-created if the Destination property is not specified or is AWS::NoValue + Type: SQS + CodeUri: ./src/ + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + + EventDestinationSQSSNS: + Type: AWS::Serverless::Function + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: SQS + Destination: !If [QueueCreationDisabled, !Ref SQSArn, !Ref 'AWS::NoValue'] + OnFailure: + Type: SNS + Destination: !If [TopicCreationEnabled, !Ref 'AWS::NoValue', 'SOME-SNS-ARN'] + CodeUri: ./src/ + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + + DestinationLambda: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + const response = { + statusCode: 200, + body: JSON.stringify('Hello from Lambda!'), + }; + return response; + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + SNSSubscription: + Type: AWS::SNS::Subscription + Condition: TopicCreationEnabled + Properties: + Endpoint: example@example.com + Protocol: email + # Refer to the auto-created SNS topic using .DestinationTopic + TopicArn: !Ref EventDestinationSQSSNS.DestinationTopic diff --git a/samtranslator/model/iam.py b/samtranslator/model/iam.py index 1184c52b6e..1cfd6f60cd 100644 --- a/samtranslator/model/iam.py +++ b/samtranslator/model/iam.py @@ -91,3 +91,31 @@ def sns_publish_role_policy(cls, topic_arn, logical_id): } } return document + + @classmethod + def event_bus_put_events_role_policy(cls, event_bus_arn, logical_id): + document = { + 'PolicyName': logical_id + 'EventBridgePolicy', + 'PolicyDocument': { + 'Statement': [{ + 'Action': 'events:PutEvents', + 'Effect': 'Allow', + 'Resource': event_bus_arn + }] + } + } + return document + + @classmethod + def lambda_invoke_function_role_policy(cls, function_arn, logical_id): + document = { + 'PolicyName': logical_id + 'LambdaPolicy', + 'PolicyDocument': { + 'Statement': [{ + 'Action': 'lambda:InvokeFunction', + 'Effect': 'Allow', + 'Resource': function_arn + }] + } + } + return document diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index 11edd9dabf..f3b56c3580 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -20,12 +20,24 @@ def fnOr(argument_list): return {'Fn::Or': argument_list} -def make_conditional(condition, data): +def fnAnd(argument_list): + return {'Fn::And': argument_list} + + +def make_conditional(condition, true_data, false_data={'Ref': 'AWS::NoValue'}): return { 'Fn::If': [ condition, - data, - {'Ref': 'AWS::NoValue'} + true_data, + false_data + ] + } + + +def make_not_conditional(condition): + return { + 'Fn::Not': [ + {'Condition': condition} ] } @@ -44,6 +56,12 @@ def make_or_condition(conditions_list): return condition +def make_and_condition(conditions_list): + and_list = make_condition_or_list(conditions_list) + condition = fnAnd(and_list) + return condition + + def calculate_number_of_conditions(conditions_length, max_conditions): """ Every condition can hold up to max_conditions, which (as of writing this) is 10. diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index e3e2ebdd86..3c643fb39f 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -92,6 +92,17 @@ class LambdaPermission(Resource): } +class LambdaEventInvokeConfig(Resource): + resource_type = 'AWS::Lambda::EventInvokeConfig' + property_types = { + 'DestinationConfig': PropertyType(False, is_type(dict)), + 'FunctionName': PropertyType(True, is_str()), + 'MaximumEventAgeInSeconds': PropertyType(False, is_type(int)), + 'MaximumRetryAttempts': PropertyType(False, is_type(int)), + 'Qualifier': PropertyType(True, is_str()) + } + + class LambdaLayerVersion(Resource): """ Lambda layer version resource """ diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 3fcead948a..2203525b70 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -20,11 +20,14 @@ from samtranslator.model.function_policies import FunctionPolicies, PolicyTypes from samtranslator.model.iam import IAMRole, IAMRolePolicies from samtranslator.model.lambda_ import (LambdaFunction, LambdaVersion, LambdaAlias, - LambdaLayerVersion) + LambdaLayerVersion, LambdaEventInvokeConfig) from samtranslator.model.types import dict_of, is_str, is_type, list_of, one_of, any_type from samtranslator.translator import logical_id_generator from samtranslator.translator.arn_generator import ArnGenerator -from samtranslator.model.intrinsics import is_intrinsic_if, is_intrinsic_no_value +from samtranslator.model.intrinsics import (is_intrinsic_if, is_intrinsic_no_value, ref, + make_not_conditional, make_conditional, make_and_condition) +from samtranslator.model.sqs import SQSQueue +from samtranslator.model.sns import SNSTopic class SamFunction(SamResourceMacro): @@ -55,6 +58,7 @@ class SamFunction(SamResourceMacro): 'DeploymentPreference': PropertyType(False, is_type(dict)), 'ReservedConcurrentExecutions': PropertyType(False, any_type()), 'Layers': PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), + 'EventInvokeConfig': PropertyType(False, is_type(dict)), # Intrinsic functions in value of Alias property are not supported, yet 'AutoPublishAlias': PropertyType(False, one_of(is_str())), @@ -67,11 +71,18 @@ class SamFunction(SamResourceMacro): # DeadLetterQueue dead_letter_queue_policy_actions = {'SQS': 'sqs:SendMessage', 'SNS': 'sns:Publish'} + # + + # Conditions + conditions = {} # Customers can refer to the following properties of SAM function referable_properties = { "Alias": LambdaAlias.resource_type, "Version": LambdaVersion.resource_type, + # EventConfig auto created SQS and SNS + "DestinationTopic": SNSTopic.resource_type, + "DestinationQueue": SQSQueue.resource_type } def resources_to_link(self, resources): @@ -93,6 +104,7 @@ def to_cloudformation(self, **kwargs): resources = [] intrinsics_resolver = kwargs["intrinsics_resolver"] mappings_resolver = kwargs.get("mappings_resolver", None) + conditions = kwargs.get("conditions", {}) if self.DeadLetterQueue: self._validate_dlq() @@ -106,6 +118,7 @@ def to_cloudformation(self, **kwargs): "AutoPublishALias must be defined on the function") lambda_alias = None + alias_name = "" if self.AutoPublishAlias: alias_name = self._get_resolved_alias_name("AutoPublishAlias", self.AutoPublishAlias, intrinsics_resolver) lambda_version = self._construct_version(lambda_function, intrinsics_resolver=intrinsics_resolver) @@ -118,6 +131,14 @@ def to_cloudformation(self, **kwargs): None), lambda_alias, intrinsics_resolver, mappings_resolver) + event_invoke_policies = [] + if self.EventInvokeConfig: + function_name = lambda_function.logical_id + event_invoke_resources, event_invoke_policies = self._construct_event_invoke_config(function_name, + alias_name, + intrinsics_resolver, + conditions) + resources.extend(event_invoke_resources) managed_policy_map = kwargs.get('managed_policy_map', {}) if not managed_policy_map: @@ -125,7 +146,7 @@ def to_cloudformation(self, **kwargs): execution_role = None if lambda_function.Role is None: - execution_role = self._construct_role(managed_policy_map) + execution_role = self._construct_role(managed_policy_map, event_invoke_policies) lambda_function.Role = execution_role.get_runtime_attr('arn') resources.append(execution_role) @@ -137,6 +158,158 @@ def to_cloudformation(self, **kwargs): return resources + def _construct_event_invoke_config(self, function_name, lambda_alias, intrinsics_resolver, conditions): + """ + Create a `AWS::Lambda::EventInvokeConfig` based on the input dict `EventInvokeConfig` + """ + resources = [] + policy_document = [] + + # Try to resolve. + resolved_event_invoke_config = intrinsics_resolver.resolve_parameter_refs(self.EventInvokeConfig) + + logical_id = "{id}EventInvokeConfig".format(id=function_name) + lambda_event_invoke_config = LambdaEventInvokeConfig(logical_id=logical_id, attributes=self.resource_attributes) + + dest_config = {} + input_dest_config = resolved_event_invoke_config.get('DestinationConfig') + if input_dest_config and \ + input_dest_config.get('OnSuccess') is not None: + resource, on_success, policy = self._validate_and_inject_resource(input_dest_config.get('OnSuccess'), + "OnSuccess", logical_id, conditions) + dest_config['OnSuccess'] = on_success + self.EventInvokeConfig['DestinationConfig']['OnSuccess']['Destination'] = on_success.get('Destination') + if resource is not None: + resources.extend([resource]) + if policy is not None: + policy_document.append(policy) + + if input_dest_config and \ + input_dest_config.get('OnFailure') is not None: + resource, on_failure, policy = self._validate_and_inject_resource(input_dest_config.get('OnFailure'), + "OnFailure", logical_id, conditions) + dest_config['OnFailure'] = on_failure + self.EventInvokeConfig['DestinationConfig']['OnFailure']['Destination'] = on_failure.get('Destination') + if resource is not None: + resources.extend([resource]) + if policy is not None: + policy_document.append(policy) + + lambda_event_invoke_config.FunctionName = ref(function_name) + if lambda_alias: + lambda_event_invoke_config.Qualifier = lambda_alias + else: + lambda_event_invoke_config.Qualifier = '$LATEST' + lambda_event_invoke_config.DestinationConfig = dest_config + lambda_event_invoke_config.MaximumEventAgeInSeconds = \ + resolved_event_invoke_config.get('MaximumEventAgeInSeconds') + lambda_event_invoke_config.MaximumRetryAttempts = resolved_event_invoke_config.get('MaximumRetryAttempts') + resources.extend([lambda_event_invoke_config]) + + return resources, policy_document + + def _validate_and_inject_resource(self, dest_config, event, logical_id, conditions): + """ + For Event Invoke Config, if the user has not specified a destination ARN for SQS/SNS, SAM + auto creates a SQS and SNS resource with defaults. Intrinsics are supported in the Destination + ARN property, so to handle conditional ifs we have to inject if conditions in the auto created + SQS/SNS resources as well as in the policy documents. + """ + accepted_types_list = ['SQS', 'SNS', 'EventBridge', 'Lambda'] + auto_inject_list = ['SQS', 'SNS'] + resource = None + policy = {} + destination = {} + destination['Destination'] = dest_config.get('Destination') + + resource_logical_id = logical_id + event + if dest_config.get('Type') is None or \ + dest_config.get('Type') not in accepted_types_list: + raise InvalidResourceException(self.logical_id, + "'Type: {}' must be one of {}" + .format(dest_config.get('Type'), accepted_types_list)) + + property_condition, dest_arn = self._get_or_make_condition(dest_config.get('Destination'), + logical_id, conditions) + if dest_config.get('Destination') is None or property_condition is not None: + combined_condition = self._make_and_conditions(self.get_passthrough_resource_attributes(), + property_condition, conditions) + if dest_config.get('Type') in auto_inject_list: + if dest_config.get('Type') == 'SQS': + resource = SQSQueue(resource_logical_id + 'Queue') + if dest_config.get('Type') == 'SNS': + resource = SNSTopic(resource_logical_id + 'Topic') + if combined_condition: + resource.set_resource_attribute('Condition', combined_condition) + if property_condition: + destination['Destination'] = make_conditional(property_condition, + resource.get_runtime_attr('arn'), + dest_arn) + else: + destination['Destination'] = resource.get_runtime_attr('arn') + policy = self._add_event_invoke_managed_policy(dest_config, resource_logical_id, property_condition, + destination['Destination']) + else: + raise InvalidResourceException(self.logical_id, + "Destination is required if Type is not {}" + .format(auto_inject_list)) + if dest_config.get('Destination') is not None and property_condition is None: + policy = self._add_event_invoke_managed_policy(dest_config, resource_logical_id, + None, dest_config.get('Destination')) + + return resource, destination, policy + + def _make_and_conditions(self, resource_condition, property_condition, conditions): + if resource_condition is None: + return property_condition + + if property_condition is None: + return resource_condition['Condition'] + + and_condition = make_and_condition([resource_condition, {'Condition': property_condition}]) + condition_name = self._make_gen_condition_name(resource_condition.get('Condition') + 'AND' + property_condition, + self.logical_id) + conditions[condition_name] = and_condition + + return condition_name + + def _get_or_make_condition(self, destination, logical_id, conditions): + """ + This method checks if there is an If condition on Destination property. Since we auto create + SQS and SNS if the destination ARN is not provided, we need to make sure that If condition + is handled here. + True case: Only create the Queue/Topic if the condition is true + Destination: !If [SomeCondition, {Ref: AWS::NoValue}, queue-arn] + + False case : Only create the Queue/Topic if the condition is false. + Destination: !If [SomeCondition, queue-arn, {Ref: AWS::NoValue}] + + For the false case, we need to add a new condition that negates the existing condition, and + add that to the top-level Conditions. + """ + if destination is None: + return None, None + if is_intrinsic_if(destination): + dest_list = destination.get('Fn::If') + if is_intrinsic_no_value(dest_list[1]) and is_intrinsic_no_value(dest_list[2]): + return None, None + if is_intrinsic_no_value(dest_list[1]): + return dest_list[0], dest_list[2] + if is_intrinsic_no_value(dest_list[2]): + condition = dest_list[0] + not_condition = self._make_gen_condition_name('NOT' + condition, logical_id) + conditions[not_condition] = make_not_conditional(condition) + return not_condition, dest_list[1] + return None, None + + def _make_gen_condition_name(self, name, hash_input): + # Make sure the property name is not over 255 characters (CFN limit) + hash_digest = logical_id_generator.LogicalIdGenerator("", hash_input).gen() + condition_name = name + hash_digest + if len(condition_name) > 255: + return input(condition_name)[:255] + return condition_name + def _get_resolved_alias_name(self, property_name, original_alias_value, intrinsics_resolver): """ Alias names can be supplied as an intrinsic function. This method tries to extract alias name from a reference @@ -195,7 +368,24 @@ def _construct_lambda_function(self): return lambda_function - def _construct_role(self, managed_policy_map): + def _add_event_invoke_managed_policy(self, dest_config, logical_id, condition, dest_arn): + policy = {} + if dest_config and dest_config.get('Type'): + if dest_config.get('Type') == 'SQS': + policy = IAMRolePolicies.sqs_send_message_role_policy(dest_arn, + logical_id) + if dest_config.get('Type') == 'SNS': + policy = IAMRolePolicies.sns_publish_role_policy(dest_arn, + logical_id) + # Event Bridge and Lambda Arns are passthrough. + if dest_config.get('Type') == 'EventBridge': + policy = IAMRolePolicies.event_bus_put_events_role_policy(dest_arn, logical_id) + if dest_config.get('Type') == 'Lambda': + policy = IAMRolePolicies.lambda_invoke_function_role_policy(dest_arn, + logical_id) + return policy + + def _construct_role(self, managed_policy_map, event_invoke_policies): """Constructs a Lambda execution role based on this SAM function's Policies property. :returns: the generated IAM Role @@ -226,8 +416,11 @@ def _construct_role(self, managed_policy_map): self.dead_letter_queue_policy_actions[self.DeadLetterQueue['Type']], self.DeadLetterQueue['TargetArn'])) - for index, policy_entry in enumerate(function_policies.get()): + if self.EventInvokeConfig: + if event_invoke_policies is not None: + policy_documents.extend(event_invoke_policies) + for index, policy_entry in enumerate(function_policies.get()): if policy_entry.type is PolicyTypes.POLICY_STATEMENT: if is_intrinsic_if(policy_entry.data): diff --git a/samtranslator/model/sns.py b/samtranslator/model/sns.py index 4b4290b946..a0d0d95ef4 100644 --- a/samtranslator/model/sns.py +++ b/samtranslator/model/sns.py @@ -1,5 +1,6 @@ from samtranslator.model import PropertyType, Resource from samtranslator.model.types import is_type, is_str +from samtranslator.model.intrinsics import ref class SNSSubscription(Resource): @@ -11,3 +12,13 @@ class SNSSubscription(Resource): 'Region': PropertyType(False, is_str()), 'FilterPolicy': PropertyType(False, is_type(dict)) } + + +class SNSTopic(Resource): + resource_type = 'AWS::SNS::Topic' + property_types = { + 'TopicName': PropertyType(False, is_str()) + } + runtime_attrs = { + "arn": lambda self: ref(self.logical_id) + } diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index ae17fecfd9..dc3803caee 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -38,7 +38,8 @@ class Globals(object): "PermissionsBoundary", "ReservedConcurrentExecutions", "ProvisionedConcurrencyConfig", - "AssumeRolePolicyDocument" + "AssumeRolePolicyDocument", + "EventInvokeConfig" ], # Everything except diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index 97e1e98d2f..379b5559d0 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -80,6 +80,8 @@ def translate(self, sam_template, parameter_values): kwargs['intrinsics_resolver'] = intrinsics_resolver kwargs['mappings_resolver'] = mappings_resolver kwargs['deployment_preference_collection'] = deployment_preference_collection + kwargs['conditions'] = template.get('Conditions') + translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references(translated, supported_resource_refs) diff --git a/tests/translator/input/error_function_with_event_dest_invalid.yaml b/tests/translator/input/error_function_with_event_dest_invalid.yaml new file mode 100644 index 0000000000..ba4ee388df --- /dev/null +++ b/tests/translator/input/error_function_with_event_dest_invalid.yaml @@ -0,0 +1,40 @@ +Parameters: + SNSArn: + Type: String + Default: my-sns-arn +Globals: + Function: + AutoPublishAlias: live + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: Lambda + OnFailure: + Type: blah + Destination: !Ref SNSArn + +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 diff --git a/tests/translator/input/error_function_with_event_dest_type.yaml b/tests/translator/input/error_function_with_event_dest_type.yaml new file mode 100644 index 0000000000..35954fce6b --- /dev/null +++ b/tests/translator/input/error_function_with_event_dest_type.yaml @@ -0,0 +1,38 @@ +Parameters: + SNSArn: + Type: String + Default: my-sns-arn +Globals: + Function: + AutoPublishAlias: live + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnFailure: + Type: blah + Destination: !Ref SNSArn + +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 diff --git a/tests/translator/input/function_with_event_dest.yaml b/tests/translator/input/function_with_event_dest.yaml new file mode 100644 index 0000000000..f2b1c4978d --- /dev/null +++ b/tests/translator/input/function_with_event_dest.yaml @@ -0,0 +1,106 @@ +Parameters: + SQSArn: + Type: String + Default: my-sqs-arn + UseExistingQueue: + Type: String + AllowedValues: + - true + - false + Default: true + CreateSNSTopic: + Type: String + AllowedValues: + - true + - false + Default: true +Conditions: + QueueCreationDisabled: !Equals [!Ref UseExistingQueue, true] + TopicCreationEnabled: !Equals [!Ref CreateSNSTopic, true] +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: SQS + Destination: !If [QueueCreationDisabled, !Ref SQSArn, !Ref 'AWS::NoValue'] + OnFailure: + Type: SNS + Destination: !If [TopicCreationEnabled, !Ref 'AWS::NoValue', 'SOME-SNS-ARN'] + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + MyTestFunction2: + Type: AWS::Serverless::Function + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: Lambda + Destination: !GetAtt DestinationLambda.Arn + OnFailure: + Type: EventBridge + Destination: event-bus-arn + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + + DestinationLambda: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + const response = { + statusCode: 200, + body: JSON.stringify('Hello from Lambda!'), + }; + return response; + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + SNSSubscription: + Type: AWS::SNS::Subscription + Condition: TopicCreationEnabled + Properties: + Endpoint: example@example.com + Protocol: email + TopicArn: !Ref MyTestFunction.DestinationTopic diff --git a/tests/translator/input/function_with_event_dest_basic.yaml b/tests/translator/input/function_with_event_dest_basic.yaml new file mode 100644 index 0000000000..88249a9e67 --- /dev/null +++ b/tests/translator/input/function_with_event_dest_basic.yaml @@ -0,0 +1,40 @@ +Parameters: + SNSArn: + Type: String + Default: my-sns-arn +Globals: + Function: + AutoPublishAlias: live + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: SQS + OnFailure: + Type: SNS + Destination: !Ref SNSArn + +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 diff --git a/tests/translator/input/function_with_event_dest_conditional.yaml b/tests/translator/input/function_with_event_dest_conditional.yaml new file mode 100644 index 0000000000..f2c1fe1ccf --- /dev/null +++ b/tests/translator/input/function_with_event_dest_conditional.yaml @@ -0,0 +1,63 @@ +Parameters: + SQSArn: + Type: String + Default: my-sqs-arn + UseExistingQueue: + Type: String + AllowedValues: + - true + - false + Default: true +Conditions: + QueueCreationDisabled: !Equals [!Ref UseExistingQueue, true] + FunctionInlineEnabled: !Equals [true, false] + FunctionCondition: !Equals [true, false] +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Condition: FunctionCondition + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: SQS + Destination: !If [QueueCreationDisabled, !Ref SQSArn, !Ref 'AWS::NoValue'] + OnFailure: + Type: Lambda + Destination: !If [FunctionInlineEnabled, !GetAtt DestinationLambda.Arn, 'some-function-arn'] + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + DestinationLambda: + Condition: FunctionInlineEnabled + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + const response = { + statusCode: 200, + body: JSON.stringify('Hello from Lambda!'), + }; + return response; + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 diff --git a/tests/translator/output/aws-cn/function_with_event_dest.json b/tests/translator/output/aws-cn/function_with_event_dest.json new file mode 100644 index 0000000000..1006bc49c3 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_event_dest.json @@ -0,0 +1,369 @@ +{ + "Conditions": { + "TopicCreationEnabled": { + "Fn::Equals": [ + { + "Ref": "CreateSNSTopic" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "CreateSNSTopic": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + }, + "SNSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": "example@example.com", + "Protocol": "email", + "TopicArn": { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + } + }, + "Condition": "TopicCreationEnabled" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + } + }, + "MyTestFunction2Role": { + "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": "MyTestFunction2EventInvokeConfigOnSuccessLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunction2EventInvokeConfigOnFailureEventBridgePolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": "event-bus-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction2EventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "event-bus-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction2" + }, + "Qualifier": "$LATEST" + } + }, + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionEventInvokeConfigOnFailureTopic": { + "Type": "AWS::SNS::Topic", + "Properties": {}, + "Condition": "TopicCreationEnabled" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_event_dest_basic.json b/tests/translator/output/aws-cn/function_with_event_dest_basic.json new file mode 100644 index 0000000000..d766dd76e7 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_event_dest_basic.json @@ -0,0 +1,146 @@ +{ + "Parameters": { + "SNSArn": { + "Default": "my-sns-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": "my-sns-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "my-sns-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "live" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionVersion948f7f815d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } + }, + "MyTestFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionVersion": { + "Fn::GetAtt": [ + "MyTestFunctionVersion948f7f815d", + "Version" + ] + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Name": "live" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_event_dest_conditional.json b/tests/translator/output/aws-cn/function_with_event_dest_conditional.json new file mode 100644 index 0000000000..e6e62d200f --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_event_dest_conditional.json @@ -0,0 +1,266 @@ +{ + "Conditions": { + "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6": { + "Fn::And": [ + { + "Condition": { + "Condition": "FunctionCondition" + } + }, + { + "Condition": { + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + } + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "FunctionCondition": { + "Fn::Equals": [ + true, + false + ] + }, + "FunctionInlineEnabled": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + }, + "Condition": "FunctionCondition" + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionCondition" + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionCondition" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_event_dest.json b/tests/translator/output/aws-us-gov/function_with_event_dest.json new file mode 100644 index 0000000000..c8dd12d705 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_event_dest.json @@ -0,0 +1,369 @@ +{ + "Conditions": { + "TopicCreationEnabled": { + "Fn::Equals": [ + { + "Ref": "CreateSNSTopic" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "CreateSNSTopic": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + }, + "SNSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": "example@example.com", + "Protocol": "email", + "TopicArn": { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + } + }, + "Condition": "TopicCreationEnabled" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + } + }, + "MyTestFunction2Role": { + "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": "MyTestFunction2EventInvokeConfigOnSuccessLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunction2EventInvokeConfigOnFailureEventBridgePolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": "event-bus-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction2EventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "event-bus-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction2" + }, + "Qualifier": "$LATEST" + } + }, + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionEventInvokeConfigOnFailureTopic": { + "Type": "AWS::SNS::Topic", + "Properties": {}, + "Condition": "TopicCreationEnabled" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json b/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json new file mode 100644 index 0000000000..a0b10f1640 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json @@ -0,0 +1,146 @@ +{ + "Parameters": { + "SNSArn": { + "Default": "my-sns-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": "my-sns-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "my-sns-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "live" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionVersion948f7f815d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } + }, + "MyTestFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionVersion": { + "Fn::GetAtt": [ + "MyTestFunctionVersion948f7f815d", + "Version" + ] + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Name": "live" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_event_dest_conditional.json b/tests/translator/output/aws-us-gov/function_with_event_dest_conditional.json new file mode 100644 index 0000000000..94cb01d323 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_event_dest_conditional.json @@ -0,0 +1,266 @@ +{ + "Conditions": { + "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6": { + "Fn::And": [ + { + "Condition": { + "Condition": "FunctionCondition" + } + }, + { + "Condition": { + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + } + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "FunctionCondition": { + "Fn::Equals": [ + true, + false + ] + }, + "FunctionInlineEnabled": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + }, + "Condition": "FunctionCondition" + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionCondition" + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionCondition" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_event_dest_invalid.json b/tests/translator/output/error_function_with_event_dest_invalid.json new file mode 100644 index 0000000000..eaebae2f4b --- /dev/null +++ b/tests/translator/output/error_function_with_event_dest_invalid.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [MyTestFunction] is invalid. Destination is required if Type is not ['SQS', 'SNS']" + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyTestFunction] is invalid. Destination is required if Type is not ['SQS', 'SNS']" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_event_dest_type.json b/tests/translator/output/error_function_with_event_dest_type.json new file mode 100644 index 0000000000..7388abec42 --- /dev/null +++ b/tests/translator/output/error_function_with_event_dest_type.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [MyTestFunction] is invalid. 'Type: blah' must be one of ['SQS', 'SNS', 'EventBridge', 'Lambda']" + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyTestFunction] is invalid. 'Type: blah' must be one of ['SQS', 'SNS', 'EventBridge', 'Lambda']" +} \ No newline at end of file diff --git a/tests/translator/output/error_globals_unsupported_property.json b/tests/translator/output/error_globals_unsupported_property.json index 473141a417..ef6cadfe99 100644 --- a/tests/translator/output/error_globals_unsupported_property.json +++ b/tests/translator/output/error_globals_unsupported_property.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig']" + "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'EventInvokeConfig']" } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument']" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig']" } diff --git a/tests/translator/output/function_with_event_dest.json b/tests/translator/output/function_with_event_dest.json new file mode 100644 index 0000000000..10c4d9ce69 --- /dev/null +++ b/tests/translator/output/function_with_event_dest.json @@ -0,0 +1,369 @@ +{ + "Conditions": { + "TopicCreationEnabled": { + "Fn::Equals": [ + { + "Ref": "CreateSNSTopic" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "CreateSNSTopic": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + }, + "SNSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": "example@example.com", + "Protocol": "email", + "TopicArn": { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + } + }, + "Condition": "TopicCreationEnabled" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "TopicCreationEnabled", + { + "Ref": "MyTestFunctionEventInvokeConfigOnFailureTopic" + }, + "SOME-SNS-ARN" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + } + }, + "MyTestFunction2Role": { + "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": "MyTestFunction2EventInvokeConfigOnSuccessLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunction2EventInvokeConfigOnFailureEventBridgePolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": "event-bus-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction2EventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "event-bus-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction2" + }, + "Qualifier": "$LATEST" + } + }, + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionEventInvokeConfigOnFailureTopic": { + "Type": "AWS::SNS::Topic", + "Properties": {}, + "Condition": "TopicCreationEnabled" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_event_dest_basic.json b/tests/translator/output/function_with_event_dest_basic.json new file mode 100644 index 0000000000..8a3c4da942 --- /dev/null +++ b/tests/translator/output/function_with_event_dest_basic.json @@ -0,0 +1,146 @@ +{ + "Parameters": { + "SNSArn": { + "Default": "my-sns-arn", + "Type": "String" + } + }, + "Resources": { + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureSNSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:publish", + "Resource": "my-sns-arn", + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + } + }, + "OnFailure": { + "Destination": "my-sns-arn" + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "live" + } + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + } + }, + "MyTestFunctionVersion948f7f815d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } + }, + "MyTestFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionVersion": { + "Fn::GetAtt": [ + "MyTestFunctionVersion948f7f815d", + "Version" + ] + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Name": "live" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_event_dest_conditional.json b/tests/translator/output/function_with_event_dest_conditional.json new file mode 100644 index 0000000000..2a499cd2ff --- /dev/null +++ b/tests/translator/output/function_with_event_dest_conditional.json @@ -0,0 +1,266 @@ +{ + "Conditions": { + "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6": { + "Fn::And": [ + { + "Condition": { + "Condition": "FunctionCondition" + } + }, + { + "Condition": { + "Condition": "NOTQueueCreationDisabled2da03e5b6f" + } + } + ] + }, + "QueueCreationDisabled": { + "Fn::Equals": [ + { + "Ref": "UseExistingQueue" + }, + true + ] + }, + "NOTQueueCreationDisabled2da03e5b6f": { + "Fn::Not": [ + { + "Condition": "QueueCreationDisabled" + } + ] + }, + "FunctionCondition": { + "Fn::Equals": [ + true, + false + ] + }, + "FunctionInlineEnabled": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "UseExistingQueue": { + "Default": true, + "Type": "String", + "AllowedValues": [ + true, + false + ] + }, + "SQSArn": { + "Default": "my-sqs-arn", + "Type": "String" + } + }, + "Resources": { + "DestinationLambdaRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionEventInvokeConfigOnSuccessQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {}, + "Condition": "FunctionConditionANDNOTQueueCreationDisabled2da03e5b6fe547d4e2d6" + }, + "MyTestFunctionEventInvokeConfig": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "MaximumEventAgeInSeconds": 70, + "MaximumRetryAttempts": 1, + "DestinationConfig": { + "OnSuccess": { + "Destination": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + } + }, + "OnFailure": { + "Destination": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + } + } + }, + "FunctionName": { + "Ref": "MyTestFunction" + }, + "Qualifier": "$LATEST" + }, + "Condition": "FunctionCondition" + }, + "MyTestFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(event, context, callback) {\n var event_received_at = new Date().toISOString();\n console.log('Event received at: ' + event_received_at);\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n if (event.Success) {\n console.log(\"Success\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(null);\n } else {\n console.log(\"Failure\");\n context.callbackWaitsForEmptyEventLoop = false;\n callback(new Error(\"Failure from event, Success = false, I am failing!\"), 'Destination Function Error Thrown');\n }\n}; \n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyTestFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionCondition" + }, + "DestinationLambda": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "DestinationLambdaRole", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "Condition": "FunctionInlineEnabled" + }, + "MyTestFunctionRole": { + "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": "MyTestFunctionEventInvokeConfigOnSuccessSQSPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::If": [ + "NOTQueueCreationDisabled2da03e5b6f", + { + "Fn::GetAtt": [ + "MyTestFunctionEventInvokeConfigOnSuccessQueue", + "Arn" + ] + }, + "my-sqs-arn" + ] + }, + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "MyTestFunctionEventInvokeConfigOnFailureLambdaPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Resource": { + "Fn::If": [ + "FunctionInlineEnabled", + { + "Fn::GetAtt": [ + "DestinationLambda", + "Arn" + ] + }, + "some-function-arn" + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "FunctionCondition" + } + } +} \ No newline at end of file diff --git a/tests/translator/test_function_resources.py b/tests/translator/test_function_resources.py index e89f76ee08..3c42a8e62b 100644 --- a/tests/translator/test_function_resources.py +++ b/tests/translator/test_function_resources.py @@ -588,6 +588,8 @@ class TestSupportedResourceReferences(TestCase): def test_must_not_break_support(self): func = SamFunction("LogicalId") - self.assertEqual(2, len(func.referable_properties)) + self.assertEqual(4, len(func.referable_properties)) self.assertEqual(func.referable_properties["Alias"], "AWS::Lambda::Alias") self.assertEqual(func.referable_properties["Version"], "AWS::Lambda::Version") + self.assertEqual(func.referable_properties["DestinationTopic"], "AWS::SNS::Topic") + self.assertEqual(func.referable_properties["DestinationQueue"], "AWS::SQS::Queue") diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index d704a53a84..6d8dca6cfd 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -263,7 +263,10 @@ class TestTranslatorEndToEnd(TestCase): 'api_with_apikey_required', 'api_with_path_parameters', 'function_with_event_source_mapping', - 'api_with_swagger_authorizer_none' + 'api_with_swagger_authorizer_none', + 'function_with_event_dest', + 'function_with_event_dest_basic', + 'function_with_event_dest_conditional' ], [ ("aws", "ap-southeast-1"), @@ -573,6 +576,9 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'error_implicit_http_api_method', 'error_implicit_http_api_path', 'error_http_api_event_multiple_same_path', + 'error_function_with_event_dest_invalid', + 'error_function_with_event_dest_type' + ]) @patch('boto3.session.Session.region_name', 'ap-southeast-1') @patch('samtranslator.plugins.application.serverless_app_plugin.ServerlessAppPlugin._sar_service_call', mock_sar_service_call) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index d653374a09..2276bbd950 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -133,6 +133,7 @@ AutoPublishAlias | `string` | Name of the Alias. Read [AutoPublishAlias Guide](. VersionDescription | `string` | A string that specifies the Description field which will be added on the new lambda version ReservedConcurrentExecutions | `integer` | The maximum of concurrent executions you want to reserve for the function. For more information see [AWS Documentation on managing concurrency](https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html) ProvisionedConcurrencyConfig | [ProvisionedConcurrencyConfig Object](#provisioned-concurrency-config-object) | Configure provisioned capacity for a number of concurrent executions on Lambda Alias property. +EventInvokeConfig | [EventInvokeConfig object](#event-invoke-config-object) | Configure options for [asynchronous invocation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html) on the function. ##### Return values @@ -855,6 +856,60 @@ Property Name | Type | Description ---|:---:|--- ProvisionedConcurrentExecutions | `string` | Number of concurrent executions to be provisioned for the Lambda function. Required parameter. +#### Event Invoke Config object + +The object describing event invoke config on a Lambda function. + +```yaml + MyFunction: + Type: 'AWS::Serverless::Function' + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: Integer (Min: 60, Max: 21600) + MaximumRetryAttempts: Integer (Min: 0, Max: 2) + DestinationConfig: + OnSuccess: + Type: [SQS | SNS | EventBridge | Function] + Destination: ARN of [SQS | SNS | EventBridge | Function] + OnFailure: + Type: [SQS | SNS | EventBridge | Function] + Destination: ARN of [SQS | SNS | EventBridge | Function] +``` + +##### Properties +Property Name | Type | Description +---|:---:|--- +MaximumEventAgeInSeconds | `integer` | The maximum age of a request that Lambda sends to a function for processing. Optional parameter. +MaximumRetryAttempts | `integer` | The maximum number of times to retry when the function returns an error. Optional parameter. +DestinationConfig | [Destination Config Object](#event-invoke-destination-config-object) | A destination for events after they have been sent to a function for processing. Optional parameter. + +#### Event Invoke Destination Config object +The object describing destination config for Event Invoke Config. + +##### Properties +Property Name | Type | Description +---|:---:|--- +OnSuccess | [Destination Config OnSuccess Object](#event-invoke-destination-config-destination-object) | A destination for events that succeeded processing. +OnFailure | [Destination Config OnFailure Object](#event-invoke-destination-config-destination-object) | A destination for events that failed processing. + +#### Event Invoke Destination Config Destination object +The object describing destination config for Event Invoke Config. + +##### Properties +Property Name | Type | Description +---|:---:|--- +Type | `string` | Type of the Resource to be invoked. Values could be [SQS | SNS | EventBridge | Lambda] +Destination | `string` | ARN of the resource to be invoked. Fn::If and Ref is supported on this property. + +The corresponding policies for the resource are generated in SAM. +Destination Property is required if Type is EventBridge and Lambda. If Type is SQS or SNS, and Destination is None, SAM auto creates these resources in the template. + +##### Generated Resources +Property Name | Type | Alias to Ref the Auto-Created Resource +---|:---:|--- +SQS | `AWS::SQS::Queue` | `.DestinationQueue` +SNS | `AWS::SNS::Topic` | `.DestinationTopic` + #### Primary key object The object describing the properties of a primary key.