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.
Short description
To 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. To do this, create a Lambda-backed custom resource in Python 3.9. The custom resource initiates a Lambda function that 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.
Resolution
Important: 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. After you delete your stack, Amazon S3 removes all notifications. If your solution appears to work, then there might be suboptimal configurations in your use case. It's a best practice to test your solution on a test S3 bucket before you deploy it 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 S3NotificationLambdaFunction 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-09
Description: >-
Sample template to illustrate use of existing S3 bucket as an event source for a Lambda function
Parameters:
NotificationBucket:
Type: String
Description: S3 bucket that's used for the Lambda event notification
Resources:
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, "CustomResourcePhysicalID")
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 NotificationBucket
2. 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-1
Important: When you launch your CloudFormation stack, you must pass in your S3 bucket. For example, run existing-bucket-for-lambda-notification.
The stack creates a Lambda function and Lambda permissions for Amazon S3. Because the stack added the required notification configuration to your S3 bucket, you can now use your S3 bucket for Lambda notifications.