How can I create an Amazon S3 notification configuration for Lambda on an existing Amazon S3 bucket using AWS CloudFormation?
Last updated: 2019-11-15
How can I use an existing Amazon Simple Storage Service (Amazon S3) bucket to create an Amazon S3 notification configuration for an AWS Lambda function using AWS CloudFormation?
Short Description
To create an Amazon S3 notification configuration, you can use AWS 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 AWS CloudFormation by using a Lambda-backed custom resource created in Python 3.6. You use a custom resource to trigger a Lambda function, which triggers the PutBucketNotification API to add a notification configuration to your S3 bucket.
Resolution
Important: The following steps apply only to S3 notification configurations for S3 buckets that don't have any existing notification configurations. If your S3 bucket already has an existing or manually-created notification configuration, the following steps will override those configurations.
1. Create a Python file named LambdaS3.py that includes the following code:
from __future__ import print_function
import json
import boto3
import urllib
from botocore.vendored import requests
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 = 'FAILURE'
responseData = {'Failure': 'Something bad happened.'}
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....")
def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):
responseUrl = event['ResponseURL']
print(responseUrl)
responseBody = {'Status': responseStatus,
'Reason': 'See the details in CloudWatch Log Stream: ' + context.log_stream_name,
'PhysicalResourceId': physicalResourceId or context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': responseData}
json_responseBody = json.dumps(responseBody)
print("Response body:\n" + json_responseBody)
headers = {
'content-type' : '',
'content-length' : str(len(json_responseBody))
}
try:
response = requests.put(responseUrl,
data=json_responseBody,
headers=headers)
print("Status code: " + response.reason)
except Exception as e:
print("send(..) failed executing requests.put(..): " + str(e))
2. Create a zip file named LambdaS3.zip for the Lambda function. See the following example:
-> LambdaS3.zip | |-> LambdaS3.py
Important: Be sure that LambdaS3.py is located at the root level of the zip file.
3. Upload the zip file to an S3 bucket that's in the same AWS Region as your AWS CloudFormation stack. To upload the zip file using the AWS Command Line Interface (AWS CLI), run the following command from the folder containing the LambdaS3.zip file:
aws s3 cp ./LambdaS3.zip s3://awsexamplebucket1/LambdaS3.zip
4. Create an AWS CloudFormation template called LambdaS3.template that includes the following code.
Important: In the following example, the S3NotificationLambdaFunction resource is the Lambda function to which the S3 notification configuration is added. CustomResourceLambdaFunction is the Lambda function used to add the S3 notification configuration for S3NotificationLambdaFunction.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
Sample template to illustrate use of existing S3 bucket as event source for
Lambda function
Parameters:
NotificationBucket:
Type: String
Description: S3 bucket which is used for Lambda event notification
LambdaCodeBucket:
Type: String
Description: S3 bucket in which custom lambda code is stored
LambdaCodeKey:
Type: String
Description: Zip file name in which custom lambda code is stored
Default: LambdaS3.zip
Lambdahandler:
Type: String
Description: Python file name which is packed inside the zip file
Default: LambdaS3
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.6
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:*'
Resource: '*'
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
CustomResourceLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: !Sub '${Lambdahandler}.lambda_handler'
Role: !GetAtt LambdaIAMRole.Arn
Code:
S3Bucket: !Ref LambdaCodeBucket
S3Key: !Ref LambdaCodeKey
Runtime: python3.6
Timeout: 50
LambdaTrigger:
Type: 'Custom::RouteTableLambda'
DependsOn: LambdaInvokePermission
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn
LambdaArn: !GetAtt S3NotificationLambdaFunction.Arn
Bucket: !Ref NotificationBucket
5. To launch an AWS CloudFormation stack with the LambdaS3.template file, use the AWS 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 ParameterKey=LambdaCodeBucket,ParameterValue=awsexamplebucket1 ParameterKey=Lambdahandler,ParameterValue=LambdaS3 ParameterKey=LambdaCodeKey,ParameterValue=LambdaS3.zip --capabilities CAPABILITY_NAMED_IAM --region us-east-1
Important: When you launch your AWS CloudFormation stack, you must pass in the following:
- Your S3 bucket (existing-bucket-for-lambda-notification)
- The S3 bucket name (awsexamplebucket1) where you uploaded the zip file
- The zip file name (LambdaS3.zip)
- The name of the file where you created the Lambda function (LambdaS3.py)
The stack creates a Lambda function and Lambda permissions for Amazon S3. Now, you can use your S3 bucket for Lambda notifications, because the stack added the required notification configuration to your S3 bucket.
Did this article help you?
Anything we could improve?
Need more help?