AWS CDK - How to add an event notification to an existing S3 Bucket

cyber-samurai picture cyber-samurai · Sep 24, 2019 · Viewed 8.2k times · Source

I'm trying to modify this AWS-provided CDK example to instead use an existing bucket. Additional documentation indicates that importing existing resources is supported. So far I am unable to add an event notification to the existing bucket using CDK.

Here is my modified version of the example:

class S3TriggerStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # create lambda function
        function = _lambda.Function(self, "lambda_function",
                                    runtime=_lambda.Runtime.PYTHON_3_7,
                                    handler="lambda-handler.main",
                                    code=_lambda.Code.asset("./lambda"))

        # **MODIFIED TO GET EXISTING BUCKET**
        #s3 = _s3.Bucket(self, "s3bucket")
        s3 = _s3.Bucket.from_bucket_arn(self, 's3_bucket',
            bucket_arn='arn:<my_region>:::<my_bucket>')

        # create s3 notification for lambda function
        notification = aws_s3_notifications.LambdaDestination(function)

        # assign notification for the s3 event type (ex: OBJECT_CREATED)
        s3.add_event_notification(_s3.EventType.OBJECT_CREATED, notification)

This results in the following error when trying to add_event_notification:

AttributeError: '_IBucketProxy' object has no attribute 'add_event_notification'

The from_bucket_arn function returns an IBucket, and the add_event_notification function is a method of the Bucket class, but I can't seem to find any other way to do this. Maybe it's not supported. Any help would be appreciated.

Answer

James Irwin picture James Irwin · Nov 7, 2019

I managed to get this working with a custom resource. It's TypeScript, but it should be easily translated to Python:

const uploadBucket = s3.Bucket.fromBucketName(this, 'BucketByName', 'existing-bucket');

const fn = new lambda.Function(this, 'MyFunction', {
    runtime: lambda.Runtime.NODEJS_10_X,
    handler: 'index.handler',
    code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler'))
});

const rsrc = new AwsCustomResource(this, 'S3NotificationResource', {
    onCreate: {
        service: 'S3',
        action: 'putBucketNotificationConfiguration',
        parameters: {
            // This bucket must be in the same region you are deploying to
            Bucket: uploadBucket.bucketName,
            NotificationConfiguration: {
                LambdaFunctionConfigurations: [
                    {
                        Events: ['s3:ObjectCreated:*'],
                        LambdaFunctionArn: fn.functionArn,
                        Filter: {
                            Key: {
                                FilterRules: [{ Name: 'suffix', Value: 'csv' }]
                            }
                        }
                    }
                ]
            }
        },
        // Always update physical ID so function gets executed
        physicalResourceId: 'S3NotifCustomResource' + Date.now().toString()
    }
});

fn.addPermission('AllowS3Invocation', {
    action: 'lambda:InvokeFunction',
    principal: new iam.ServicePrincipal('s3.amazonaws.com'),
    sourceArn: uploadBucket.bucketArn
});

rsrc.node.addDependency(fn.permissionsNode.findChild('AllowS3Invocation'));

This is basically a CDK version of the CloudFormation template laid out in this example. See the docs on the AWS SDK for the possible NotificationConfiguration parameters.