Skip to content

Commit

Permalink
feat(aws-s3): Bucket Notifications (#201)
Browse files Browse the repository at this point in the history
Adds support for S3 bucket notifications. The `bucket.onEvent`
method will add a notification destination for a bucket.

The s3.INotificationDestination interface is used
to allow SNS, SQS and Lambda to implement notification
destinations. This interface inverts the control and
allows the destination to prepare to receive notifications.
For example, it can modify it's policy appropriately.

Since CloudFormation bucket notification support require two-phase
deployments (due to the fact PutBucketNotification will fail if
the destination policy has not been updated, and CloudFormation
cannot create the policy until the bucket is created). The reason this
is a limitation in CloudFormation is that they could not model the
1:1 relationship between the bucket and the notifications using the
current semantics of CloudFormation.

In the CDK, we can model this relationship by encapsulating the
notifications custom resource behind a bucket. This means that users
don't interact with this resource directly, but rather just subscribe
to notifications on a bucket, and the resource (and accompanying handler)
will be created as needed.
  • Loading branch information
Elad Ben-Israel authored Aug 13, 2018
1 parent a283a39 commit 8cd07e6
Show file tree
Hide file tree
Showing 11 changed files with 1,199 additions and 0 deletions.
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,40 @@ new Consumer(app, 'consume', {

process.stdout.write(app.run());
```

### Bucket Notifications

The Amazon S3 notification feature enables you to receive notifications when
certain events happen in your bucket as described under [S3 Bucket
Notifications] of the S3 Developer Guide.

To subscribe for bucket notifications, use the `bucket.onEvent` method. The
`bucket.onObjectCreated` and `bucket.onObjectRemoved` can also be used for these
common use cases.

The following example will subscribe an SNS topic to be notified of all
``s3:ObjectCreated:*` events:

```ts
const myTopic = new sns.Topic(this, 'MyTopic');
bucket.onEvent(s3.EventType.ObjectCreated, myTopic);
```

This call will also ensure that the topic policy can accept notifications for
this specific bucket.

The following destinations are currently supported:

* `sns.Topic`
* `sqs.Queue`
* `lambda.Function`

It is also possible to specify S3 object key filters when subscribing. The
following example will notify `myQueue` when objects prefixed with `foo/` and
have the `.jpg` suffix are removed from the bucket.

```ts
bucket.onEvent(s3.EventType.ObjectRemoved, myQueue, { prefix: 'foo/', suffix: '.jpg' });
```

[S3 Bucket Notifications]: https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
174 changes: 174 additions & 0 deletions packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import cdk = require('@aws-cdk/cdk');
import { BucketPolicy } from './bucket-policy';
import { IBucketNotificationDestination } from './notification-dest';
import { BucketNotifications } from './notifications-resource';
import perms = require('./perms');
import { LifecycleRule } from './rule';
import { BucketArn, BucketDomainName, BucketDualStackDomainName, cloudformation } from './s3.generated';
Expand Down Expand Up @@ -289,6 +291,7 @@ export class Bucket extends BucketRef {
protected autoCreatePolicy = true;
private readonly lifecycleRules: LifecycleRule[] = [];
private readonly versioned?: boolean;
private readonly notifications: BucketNotifications;

constructor(parent: cdk.Construct, name: string, props: BucketProps = {}) {
super(parent, name);
Expand Down Expand Up @@ -316,6 +319,10 @@ export class Bucket extends BucketRef {

// Add all lifecycle rules
(props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this));

// defines a BucketNotifications construct. Notice that an actual resource will only
// be added if there are notifications added, so we don't need to condition this.
this.notifications = new BucketNotifications(this, 'Notifications', { bucket: this });
}

/**
Expand All @@ -333,6 +340,53 @@ export class Bucket extends BucketRef {
this.lifecycleRules.push(rule);
}

/**
* Adds a bucket notification event destination.
* @param event The event to trigger the notification
* @param dest The notification destination (Lambda, SNS Topic or SQS Queue)
*
* @param filters S3 object key filter rules to determine which objects
* trigger this event. Each filter must include a `prefix` and/or `suffix`
* that will be matched against the s3 object key. Refer to the S3 Developer Guide
* for details about allowed filter rules.
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering
*
* @example
*
* bucket.onEvent(EventType.OnObjectCreated, myLambda, 'home/myusername/*')
*
* @see
* https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
*/
public onEvent(event: EventType, dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) {
this.notifications.addNotification(event, dest, ...filters);
}

/**
* Subscribes a destination to receive notificatins when an object is
* created in the bucket. This is identical to calling
* `onEvent(EventType.ObjectCreated)`.
*
* @param dest The notification destination (see onEvent)
* @param filters Filters (see onEvent)
*/
public onObjectCreated(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) {
return this.onEvent(EventType.ObjectCreated, dest, ...filters);
}

/**
* Subscribes a destination to receive notificatins when an object is
* removed from the bucket. This is identical to calling
* `onEvent(EventType.ObjectRemoved)`.
*
* @param dest The notification destination (see onEvent)
* @param filters Filters (see onEvent)
*/
public onObjectRemoved(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) {
return this.onEvent(EventType.ObjectRemoved, dest, ...filters);
}

/**
* Set up key properties and return the Bucket encryption property from the
* user's configuration.
Expand Down Expand Up @@ -485,6 +539,126 @@ export class S3Url extends cdk.Token {

}

/**
* Notification event types.
*/
export enum EventType {
/**
* Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
* these event types, you can enable notification when an object is created
* using a specific API, or you can use the s3:ObjectCreated:* event type to
* request notification regardless of the API that was used to create an
* object.
*/
ObjectCreated = 's3:ObjectCreated:*',

/**
* Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
* these event types, you can enable notification when an object is created
* using a specific API, or you can use the s3:ObjectCreated:* event type to
* request notification regardless of the API that was used to create an
* object.
*/
ObjectCreatedPut = 's3:ObjectCreated:Put',

/**
* Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
* these event types, you can enable notification when an object is created
* using a specific API, or you can use the s3:ObjectCreated:* event type to
* request notification regardless of the API that was used to create an
* object.
*/
ObjectCreatedPost = 's3:ObjectCreated:Post',

/**
* Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
* these event types, you can enable notification when an object is created
* using a specific API, or you can use the s3:ObjectCreated:* event type to
* request notification regardless of the API that was used to create an
* object.
*/
ObjectCreatedCopy = 's3:ObjectCreated:Copy',

/**
* Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
* these event types, you can enable notification when an object is created
* using a specific API, or you can use the s3:ObjectCreated:* event type to
* request notification regardless of the API that was used to create an
* object.
*/
ObjectCreatedCompleteMultipartUpload = 's3:ObjectCreated:CompleteMultipartUpload',

/**
* By using the ObjectRemoved event types, you can enable notification when
* an object or a batch of objects is removed from a bucket.
*
* You can request notification when an object is deleted or a versioned
* object is permanently deleted by using the s3:ObjectRemoved:Delete event
* type. Or you can request notification when a delete marker is created for
* a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
* information about deleting versioned objects, see Deleting Object
* Versions. You can also use a wildcard s3:ObjectRemoved:* to request
* notification anytime an object is deleted.
*
* You will not receive event notifications from automatic deletes from
* lifecycle policies or from failed operations.
*/
ObjectRemoved = 's3:ObjectRemoved:*',

/**
* By using the ObjectRemoved event types, you can enable notification when
* an object or a batch of objects is removed from a bucket.
*
* You can request notification when an object is deleted or a versioned
* object is permanently deleted by using the s3:ObjectRemoved:Delete event
* type. Or you can request notification when a delete marker is created for
* a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
* information about deleting versioned objects, see Deleting Object
* Versions. You can also use a wildcard s3:ObjectRemoved:* to request
* notification anytime an object is deleted.
*
* You will not receive event notifications from automatic deletes from
* lifecycle policies or from failed operations.
*/
ObjectRemovedDelete = 's3:ObjectRemoved:Delete',

/**
* By using the ObjectRemoved event types, you can enable notification when
* an object or a batch of objects is removed from a bucket.
*
* You can request notification when an object is deleted or a versioned
* object is permanently deleted by using the s3:ObjectRemoved:Delete event
* type. Or you can request notification when a delete marker is created for
* a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
* information about deleting versioned objects, see Deleting Object
* Versions. You can also use a wildcard s3:ObjectRemoved:* to request
* notification anytime an object is deleted.
*
* You will not receive event notifications from automatic deletes from
* lifecycle policies or from failed operations.
*/
ObjectRemovedDeleteMarkerCreated = 's3:ObjectRemoved:DeleteMarkerCreated',

/**
* You can use this event type to request Amazon S3 to send a notification
* message when Amazon S3 detects that an object of the RRS storage class is
* lost.
*/
ReducedRedundancyLostObject = 's3:ReducedRedundancyLostObject',
}

export interface NotificationKeyFilter {
/**
* S3 keys must have the specified prefix.
*/
prefix?: string;

/**
* S3 keys must have the specified suffix.
*/
suffix?: string;
}

class ImportedBucketRef extends BucketRef {
public readonly bucketArn: BucketArn;
public readonly bucketName: BucketName;
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-s3/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './bucket';
export * from './bucket-policy';
export * from './rule';
export * from './notification-dest';

// AWS::S3 CloudFormation Resources:
export * from './s3.generated';
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-s3/lib/notification-dest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import cdk = require('@aws-cdk/cdk');
import { Bucket } from './bucket';

/**
* Implemented by constructs that can be used as bucket notification destinations.
*/
export interface IBucketNotificationDestination {
/**
* Registers this resource to receive notifications for the specified bucket.
* @param bucket The bucket. Use the `path` of the bucket as a unique ID.
*/
asBucketNotificationDestination(bucket: Bucket): BucketNotificationDestinationProps;
}

/**
* Represents the properties of a notification destination.
*/
export interface BucketNotificationDestinationProps {
/**
* The notification type.
*/
readonly type: BucketNotificationDestinationType;

/**
* The ARN of the destination (i.e. Lambda, SNS, SQS).
*/
readonly arn: cdk.Arn;
}

/**
* Supported types of notification destinations.
*/
export enum BucketNotificationDestinationType {
Lambda,
Queue,
Topic
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './notifications-resource';
Loading

0 comments on commit 8cd07e6

Please sign in to comment.