Question: How can I use CloudFormation to create an Amazon S3 notification configuration for Lambda on an existing S3 bucket?

I want to use an existing Amazon Simple Storage Service (Amazon S3) bucket to create an Amazon S3 notification configuration for an AWS Lambda function. I want to do this using AWS CloudFormation.

Short descriptionTo create an Amazon S3 notification configuration, use CloudFormation to create a new S3 bucket. Then, add a notification configuration to that bucket using the NotificationConfiguration property. Or, manually add a notification configuration to an existing S3 bucket.The following steps show you how to add a notification configuration to your existing S3 bucket with CloudFormation. You can do this by using a Lambda-backed custom resource created in Python 3.9. The custom resource initiates a Lambda function, which then starts the PutBucketNotification API to add a notification configuration to your S3 bucket.Note: If you receive errors when running AWS Command Line Interface (AWS CLI) commands, make sure that you’re using the most recent AWS CLI version.

ResolutionImportant: The following steps apply only to Amazon S3 notification configurations for S3 buckets that don't have existing notification configurations. If your S3 bucket already has an existing or a manually created notification configuration, then the following steps override those configurations. All notifications are removed after you delete your stack. If your solution appears to work, there might be suboptimal configurations related to your use case. It's a best practice to test your solution on a test S3 bucket before deploying to a production environment.1.    Create a CloudFormation template called LambdaS3.template that includes the following code.Important: In the following example, you add the S3 notification configuration to the S3Notification****LambdaFunction resource. You use the Lambda function CustomResourceLambdaFunction to add the S3 notification configuration for S3NotificationLambdaFunction. To meet your requirements, you can modify the code in the CustomResourceLambdaFunction resource.AWSTemplateFormatVersion: 2010-09-09Description: >-  Sample template to illustrate use of existing S3 bucket as an event source for a Lambda functionParameters:  NotificationBucket:    Type: String    Description: S3 bucket that's used for the Lambda event notificationResources:  S3NotificationLambdaFunction:    Type: 'AWS::Lambda::Function'    Properties:      Code:        ZipFile: !Join          - |+          - - import json            - 'def lambda_handler(event,context):'            - '    return ''Welcome... This is a test Lambda Function'''      Handler: index.lambda_handler      Role: !GetAtt LambdaIAMRole.Arn      Runtime: python3.9      Timeout: 5  LambdaInvokePermission:    Type: 'AWS::Lambda::Permission'    Properties:      FunctionName: !GetAtt S3NotificationLambdaFunction.Arn      Action: 'lambda:InvokeFunction'      Principal: s3.amazonaws.com      SourceAccount: !Ref 'AWS::AccountId'      SourceArn: !Sub 'arn:aws:s3:::${NotificationBucket}'  LambdaIAMRole:    Type: 'AWS::IAM::Role'    Properties:      AssumeRolePolicyDocument:        Version: 2012-10-17        Statement:          - Effect: Allow            Principal:              Service:                - lambda.amazonaws.com            Action:              - 'sts:AssumeRole'      Path: /      Policies:        - PolicyName: root          PolicyDocument:            Version: 2012-10-17            Statement:              - Effect: Allow                Action:                  - 's3:GetBucketNotification'                  - 's3:PutBucketNotification'                Resource: !Sub 'arn:aws:s3:::${NotificationBucket}'              - Effect: Allow                Action:                  - 'logs:CreateLogGroup'                  - 'logs:CreateLogStream'                  - 'logs:PutLogEvents'                Resource: 'arn:aws:logs:*:*:*'  CustomResourceLambdaFunction:    Type: 'AWS::Lambda::Function'    Properties:      Handler: index.lambda_handler      Role: !GetAtt LambdaIAMRole.Arn      Code:        ZipFile: |            from __future__ import print_function            import json            import boto3            import cfnresponse                        SUCCESS = "SUCCESS"            FAILED = "FAILED"                        print('Loading function')            s3 = boto3.resource('s3')                        def lambda_handler(event, context):                print("Received event: " + json.dumps(event, indent=2))                responseData={}                try:                    if event['RequestType'] == 'Delete':                        print("Request Type:",event['RequestType'])                        Bucket=event['ResourceProperties']['Bucket']                        delete_notification(Bucket)                        print("Sending response to custom resource after Delete")                    elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update':                        print("Request Type:",event['RequestType'])                        LambdaArn=event['ResourceProperties']['LambdaArn']                        Bucket=event['ResourceProperties']['Bucket']                        add_notification(LambdaArn, Bucket)                        responseData={'Bucket':Bucket}                        print("Sending response to custom resource")                    responseStatus = 'SUCCESS'                except Exception as e:                    print('Failed to process:', e)                    responseStatus = 'FAILED'                    responseData = {'Failure': 'Something bad happened.'}                cfnresponse.send(event, context, responseStatus, responseData)            def add_notification(LambdaArn, Bucket):                bucket_notification = s3.BucketNotification(Bucket)                response = bucket_notification.put(                  NotificationConfiguration={                    'LambdaFunctionConfigurations': [                      {                          'LambdaFunctionArn': LambdaArn,                          'Events': [                              's3:ObjectCreated:*'                          ]                      }                    ]                  }                )                print("Put request completed....")                          def delete_notification(Bucket):                bucket_notification = s3.BucketNotification(Bucket)                response = bucket_notification.put(                    NotificationConfiguration={}                )                print("Delete request completed....")      Runtime: python3.9      Timeout: 50  LambdaTrigger:    Type: 'Custom::LambdaTrigger'    DependsOn: LambdaInvokePermission    Properties:      ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn      LambdaArn: !GetAtt S3NotificationLambdaFunction.Arn      Bucket: !Ref NotificationBucket2.    To launch a CloudFormation stack with the LambdaS3.template file, use the CloudFormation console or the following AWS CLI command:aws cloudformation create-stack --stack-name lambda-s3-notification --template-body file://LambdaS3.template --parameters ParameterKey=NotificationBucket,ParameterValue=existing-bucket-for-lambda-notification --capabilities CAPABILITY_NAMED_IAM --region us-east-1Important: When you launch your CloudFormation stack, you must pass in your S3 bucket. For example: existing-bucket-for-lambda-notificationThe stack creates a Lambda function and Lambda permissions for Amazon S3. This means that you can now use your S3 bucket for Lambda notifications because the stack added the required notification configuration to your S3 bucket.