diff --git a/integration/combination/test_function_with_s3_bucket.py b/integration/combination/test_function_with_s3_bucket.py index 8e04d41d2..90ddb3016 100644 --- a/integration/combination/test_function_with_s3_bucket.py +++ b/integration/combination/test_function_with_s3_bucket.py @@ -15,4 +15,18 @@ def test_function_with_s3_bucket_trigger(self): # There should be only One notification configuration for the event self.assertEqual(len(configurations), 1) config = configurations[0] - self.assertEqual(set(config["Events"]), {"s3:ObjectCreated:*"}) + self.assertEqual(config["Events"], ["s3:ObjectCreated:*"]) + + def test_function_with_s3_bucket_intrinsics(self): + self.create_and_verify_stack("combination/function_with_s3_intrinsics") + + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + + self.assertEqual(len(configurations), 1) + config = configurations[0] + self.assertEqual(config["Events"], ["s3:ObjectCreated:*"]) + self.assertEqual(config["Filter"]["Key"]["FilterRules"], [{"Name": "Suffix", "Value": "object_suffix"}]) diff --git a/integration/resources/expected/combination/function_with_s3_intrinsics.json b/integration/resources/expected/combination/function_with_s3_intrinsics.json new file mode 100644 index 000000000..2fdb0626a --- /dev/null +++ b/integration/resources/expected/combination/function_with_s3_intrinsics.json @@ -0,0 +1,18 @@ +[ + { + "LogicalResourceId": "MyLambdaFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyLambdaFunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MyLambdaFunctionS3EventPermission", + "ResourceType": "AWS::Lambda::Permission" + }, + { + "LogicalResourceId": "MyBucket", + "ResourceType": "AWS::S3::Bucket" + } +] \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_s3_intrinsics.yaml b/integration/resources/templates/combination/function_with_s3_intrinsics.yaml new file mode 100644 index 000000000..8de86f083 --- /dev/null +++ b/integration/resources/templates/combination/function_with_s3_intrinsics.yaml @@ -0,0 +1,36 @@ +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: MyBucket + Events: s3:ObjectCreated:* + Filter: + Fn::If: + - MyCondition + - S3Key: + Rules: + - Name: prefix + Value: object_prefix + - S3Key: + Rules: + - Name: suffix + Value: object_suffix + + MyBucket: + Type: AWS::S3::Bucket diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 73b1df299..1cd7d9276 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -244,7 +244,7 @@ class S3(PushEventSource): principal = "s3.amazonaws.com" property_types = { "Bucket": PropertyType(True, is_str()), - "Events": PropertyType(True, one_of(is_str(), list_of(is_str()))), + "Events": PropertyType(True, one_of(is_str(), list_of(is_str())), False), "Filter": PropertyType(False, dict_of(is_str(), is_str())), } diff --git a/tests/translator/input/error_function_invalid_s3_event.yaml b/tests/translator/input/error_function_invalid_s3_event.yaml new file mode 100644 index 000000000..3fd48f82a --- /dev/null +++ b/tests/translator/input/error_function_invalid_s3_event.yaml @@ -0,0 +1,23 @@ +Parameters: + EventsParam: + Type: String + Default: s3:ObjectCreated:* + +Resources: + FunctionInvalidS3Event: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: MyBucket + Events: + Ref: EventsParam + + MyBucket: + Type: AWS::S3::Bucket diff --git a/tests/translator/input/s3_intrinsics.yaml b/tests/translator/input/s3_intrinsics.yaml new file mode 100644 index 000000000..dff8fe980 --- /dev/null +++ b/tests/translator/input/s3_intrinsics.yaml @@ -0,0 +1,40 @@ +Parameters: + EventsParam: + Type: String + Default: s3:ObjectCreated:* + +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + ThumbnailFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/thumbnails.zip + Handler: index.generate_thumbails + Runtime: nodejs12.x + Events: + ImageBucket: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: + - s3:ObjectCreated:* + Filter: + Fn::If: + - MyCondition + - S3Key: + Rules: + - Name: Rule1Prefix + Value: Rule1Value + - S3Key: + Rules: + - Name: Rule2Prefix + Value: Rule2Value + + Images: + Type: AWS::S3::Bucket diff --git a/tests/translator/output/aws-cn/s3_intrinsics.json b/tests/translator/output/aws-cn/s3_intrinsics.json new file mode 100644 index 000000000..834251134 --- /dev/null +++ b/tests/translator/output/aws-cn/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/s3_intrinsics.json b/tests/translator/output/aws-us-gov/s3_intrinsics.json new file mode 100644 index 000000000..947ec4ef8 --- /dev/null +++ b/tests/translator/output/aws-us-gov/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_invalid_s3_event.json b/tests/translator/output/error_function_invalid_s3_event.json new file mode 100644 index 000000000..d76259073 --- /dev/null +++ b/tests/translator/output/error_function_invalid_s3_event.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionInvalidS3EventS3Event] is invalid. Type of property 'Events' is invalid." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionInvalidS3EventS3Event] is invalid. Type of property 'Events' is invalid." +} \ No newline at end of file diff --git a/tests/translator/output/s3_intrinsics.json b/tests/translator/output/s3_intrinsics.json new file mode 100644 index 000000000..6b5e52f3e --- /dev/null +++ b/tests/translator/output/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 0db19f45a..c9efa804f 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -329,6 +329,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "s3_existing_lambda_notification_configuration", "s3_existing_other_notification_configuration", "s3_filter", + "s3_intrinsics", "s3_multiple_events_same_bucket", "s3_multiple_functions", "s3_with_dependsOn", @@ -693,6 +694,7 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_cors_credentials_true_without_explicit_origin", "error_function_invalid_codeuri", "error_function_invalid_api_event", + "error_function_invalid_s3_event", "error_function_invalid_autopublishalias", "error_function_invalid_event_type", "error_function_invalid_layer",