From 625aef43d111d0a12d72fbf709feeb244267bbff Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Fri, 29 Jun 2018 17:26:58 -0700 Subject: [PATCH] Initial work on bucket notifications Remaining work: - [ ] Define a custom resource to break the cycle - [ ] Add `IBucketNotificationTarget` on SQS --- packages/@aws-cdk/lambda/lib/lambda-ref.ts | 10 +- packages/@aws-cdk/s3/lib/bucket.ts | 96 ++++++++++- packages/@aws-cdk/s3/lib/index.ts | 3 +- packages/@aws-cdk/s3/lib/notifications.ts | 143 ++++++++++++++++ .../s3/test/integ.bucket.expected.json | 2 +- .../s3/test/integ.notifications.expected.json | 36 ++++ .../@aws-cdk/s3/test/integ.notifications.ts | 21 +++ .../@aws-cdk/s3/test/test.notifications.ts | 160 ++++++++++++++++++ packages/@aws-cdk/sns/lib/topic-ref.ts | 25 ++- packages/@aws-cdk/sns/package.json | 3 +- .../integ.bucket-notification.expected.json | 32 ++++ .../sns/test/integ.bucket-notification.ts | 14 ++ .../integ.bucket-notifications.expected.json | 1 + packages/@aws-cdk/sns/test/test.sns.ts | 34 ++++ 14 files changed, 572 insertions(+), 8 deletions(-) create mode 100644 packages/@aws-cdk/s3/lib/notifications.ts create mode 100644 packages/@aws-cdk/s3/test/integ.notifications.expected.json create mode 100644 packages/@aws-cdk/s3/test/integ.notifications.ts create mode 100644 packages/@aws-cdk/s3/test/test.notifications.ts create mode 100644 packages/@aws-cdk/sns/test/integ.bucket-notification.expected.json create mode 100644 packages/@aws-cdk/sns/test/integ.bucket-notification.ts create mode 100644 packages/@aws-cdk/sns/test/integ.bucket-notifications.expected.json diff --git a/packages/@aws-cdk/lambda/lib/lambda-ref.ts b/packages/@aws-cdk/lambda/lib/lambda-ref.ts index 8efa0cb0351af..2e891ebd8d28c 100644 --- a/packages/@aws-cdk/lambda/lib/lambda-ref.ts +++ b/packages/@aws-cdk/lambda/lib/lambda-ref.ts @@ -3,6 +3,7 @@ import { AccountPrincipal, Arn, Construct, FnSelect, FnSplit, PolicyPrincipal, import { EventRuleTarget, IEventRuleTarget } from '@aws-cdk/events'; import { Role } from '@aws-cdk/iam'; import { lambda } from '@aws-cdk/resources'; +import { BucketNotificationTarget, BucketNotificationTargetType, IBucketNotificationTarget } from '@aws-cdk/s3'; import { LambdaPermission } from './permission'; /** @@ -22,7 +23,7 @@ export interface LambdaRefProps { role?: Role; } -export abstract class LambdaRef extends Construct implements IEventRuleTarget { +export abstract class LambdaRef extends Construct implements IEventRuleTarget, IBucketNotificationTarget { /** * Creates a Lambda function object which represents a function not defined * within this stack. @@ -135,6 +136,13 @@ export abstract class LambdaRef extends Construct implements IEventRuleTarget { arn: this.functionArn, }; } + + public get bucketNotificationTarget(): BucketNotificationTarget { + return { + type: BucketNotificationTargetType.Lambda, + arn: this.functionArn + }; + } } class LambdaRefImport extends LambdaRef { diff --git a/packages/@aws-cdk/s3/lib/bucket.ts b/packages/@aws-cdk/s3/lib/bucket.ts index 56fd4efce7592..f2a60c0719f86 100644 --- a/packages/@aws-cdk/s3/lib/bucket.ts +++ b/packages/@aws-cdk/s3/lib/bucket.ts @@ -3,6 +3,7 @@ import { IIdentityResource } from '@aws-cdk/iam'; import * as kms from '@aws-cdk/kms'; import { s3 } from '@aws-cdk/resources'; import { BucketPolicy } from './bucket-policy'; +import { EventType, IBucketNotificationTarget, BucketNotificationTargetType } from './notifications'; import * as perms from './perms'; import { LifecycleRule } from './rule'; import { parseBucketArn, parseBucketName, validateBucketName } from './util'; @@ -257,6 +258,7 @@ export class Bucket extends BucketRef { protected autoCreatePolicy = true; private readonly lifecycleRules: LifecycleRule[] = []; private readonly versioned?: boolean; + private readonly notifications = new Array(); constructor(parent: Construct, name: string, props: BucketProps = {}) { super(parent, name); @@ -269,7 +271,8 @@ export class Bucket extends BucketRef { bucketName: props && props.bucketName, bucketEncryption, versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined, - lifecycleConfiguration: new Token(() => this.parseLifecycleConfiguration()), + lifecycleConfiguration: new Token(() => this.renderLifecycleConfiguration()), + notificationConfiguration: new Token(() => this.renderNotificationConfiguration()) }); applyRemovalPolicy(resource, props.removalPolicy); @@ -301,6 +304,27 @@ export class Bucket extends BucketRef { this.lifecycleRules.push(rule); } + /** + * Adds a bucket notification event target. + * @param event The event to trigger the notification + * @param target The target (Lambda, SNS Topic or SQS Queue) + * + * @param filterRules S3 filter rules to determine which objects trigger + * this event. Rules must include either a prefix asterisk ("*foo/bar") or + * suffix asterisk ("foo/bar*") to indicate if this is a prefix or a suffix + * rule. + * + * @example + * + * bucket.onEvent(EventType.OnObjectCreated, myLambda, 'home/myusername/*') + * + * @see + * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html + */ + public onEvent(event: EventType, target: IBucketNotificationTarget, ...filterRules: string[]) { + this.notifications.push({ event, target, filterRules }); + } + /** * Set up key properties and return the Bucket encryption property from the * user's configuration. @@ -353,10 +377,10 @@ export class Bucket extends BucketRef { } /** - * Parse the lifecycle configuration out of the uucket props + * Render the lifecycle configuration based on bucket props * @param props Par */ - private parseLifecycleConfiguration(): s3.BucketResource.LifecycleConfigurationProperty | undefined { + private renderLifecycleConfiguration(): s3.BucketResource.LifecycleConfigurationProperty | undefined { if (!this.lifecycleRules || this.lifecycleRules.length === 0) { return undefined; } @@ -394,6 +418,66 @@ export class Bucket extends BucketRef { })); } } + + private renderNotificationConfiguration(): s3.BucketResource.NotificationConfigurationProperty | undefined { + if (this.notifications.length === 0) { + return undefined; + } + + const lambda = new Array(); + const queue = new Array(); + const topic = new Array(); + + for (const notification of this.notifications) { + const event = notification .event; + const filter = renderFilter(notification.filterRules); + const type = notification.target.bucketNotificationTarget.type; + const arn = notification.target.bucketNotificationTarget.arn; + switch (type) { + case BucketNotificationTargetType.Lambda: + lambda.push({ event, filter, function: arn }); + break; + case BucketNotificationTargetType.Topic: + topic.push({ event, filter, topic: arn }); + break; + case BucketNotificationTargetType.Queue: + queue.push({ event, filter, queue: arn }); + break; + default: + throw new Error('Unsupported notification target type:' + notification.target.bucketNotificationTargetType); + } + } + + return { + lambdaConfigurations: lambda.length > 0 ? lambda : undefined, + queueConfigurations: queue.length > 0 ? queue : undefined, + topicConfigurations: topic.length > 0 ? topic : undefined + }; + + function renderFilter(rules?: string[]): s3.BucketResource.NotificationFilterProperty | undefined { + if (!rules || rules.length === 0) { + return undefined; + } + + const renderedRules = new Array(); + + for (const rule of rules) { + if (rule.startsWith('*')) { + renderedRules.push({ name: 'suffix', value: rule.substr(1) }); + } else if (rule.endsWith('*')) { + renderedRules.push({ name: 'prefix', value: rule.substr(0, rule.length - 1) }); + } else { + throw new Error('Rule must either have a "*" prefix or suffix to indicate the rule type: ' + rule); + } + } + + return { + s3Key: { + rules: renderedRules + } + }; + } + } } /** @@ -441,3 +525,9 @@ class ImportedBucketRef extends BucketRef { this.policy = undefined; } } + +interface BucketNotification { + event: EventType; + target: IBucketNotificationTarget; + filterRules?: string[]; +} diff --git a/packages/@aws-cdk/s3/lib/index.ts b/packages/@aws-cdk/s3/lib/index.ts index d541a5be32440..4c421b998eb5e 100644 --- a/packages/@aws-cdk/s3/lib/index.ts +++ b/packages/@aws-cdk/s3/lib/index.ts @@ -1,3 +1,4 @@ export * from './bucket'; export * from './bucket-policy'; -export * from './rule'; \ No newline at end of file +export * from './rule'; +export * from './notifications'; \ No newline at end of file diff --git a/packages/@aws-cdk/s3/lib/notifications.ts b/packages/@aws-cdk/s3/lib/notifications.ts new file mode 100644 index 0000000000000..e30fcf65d9517 --- /dev/null +++ b/packages/@aws-cdk/s3/lib/notifications.ts @@ -0,0 +1,143 @@ +import { Arn } from "@aws-cdk/core"; +import { s3 } from '@aws-cdk/resources'; + +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 BucketNotificationProps { + /** + * The S3 bucket event. + */ + event: EventType; + + /** + * The filtering rule that determine which objects trigger the event. For + * example, you can create a filter so that only image files with a .jpg + * extension trigger the event when they are added to the S3 bucket. + * + * @default All objects will trigger the event + */ + s3KeyFilter?: string; + + /** + * The target of the notification. + */ + target: IBucketNotificationTarget; +} + +export interface IBucketNotificationTarget { + bucketNotificationTarget(bucketArn: s3.BucketArn): BucketNotificationTarget; +} + +export interface BucketNotificationTarget { + readonly type: BucketNotificationTargetType; + readonly arn: Arn; +} + +export enum BucketNotificationTargetType { + Lambda, + Queue, + Topic +} diff --git a/packages/@aws-cdk/s3/test/integ.bucket.expected.json b/packages/@aws-cdk/s3/test/integ.bucket.expected.json index 738573fd3fac7..64b6d6492438a 100644 --- a/packages/@aws-cdk/s3/test/integ.bucket.expected.json +++ b/packages/@aws-cdk/s3/test/integ.bucket.expected.json @@ -157,4 +157,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/s3/test/integ.notifications.expected.json b/packages/@aws-cdk/s3/test/integ.notifications.expected.json new file mode 100644 index 0000000000000..153cb4fa03e55 --- /dev/null +++ b/packages/@aws-cdk/s3/test/integ.notifications.expected.json @@ -0,0 +1,36 @@ +{ + "Resources": { + "TestBucket560B80BC": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "TopicConfigurations": [ + { + "Event": "s3:ObjectCreated:*", + "Filter": { + "S3Key": { + "Rules": [ + { + "Name": "prefix", + "Value": "images/" + }, + { + "Name": "suffix", + "Value": ".jpg" + } + ] + } + }, + "Topic": { + "Ref": "Topic" + } + } + ] + } + } + }, + "Topic": { + "Type": "AWS::SNS::Topic" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/s3/test/integ.notifications.ts b/packages/@aws-cdk/s3/test/integ.notifications.ts new file mode 100644 index 0000000000000..5639edaa20689 --- /dev/null +++ b/packages/@aws-cdk/s3/test/integ.notifications.ts @@ -0,0 +1,21 @@ +import { App, Resource, Stack } from '@aws-cdk/core'; +import { Bucket, EventType, BucketNotificationTargetType } from '../lib'; + +const app = new App(process.argv); + +const stack = new Stack(app, 'aws-cdk-s3-bucket-notifications'); + +const bucket = new Bucket(stack, 'TestBucket'); + +const topic = new Resource(stack, 'Topic', { + type: 'AWS::SNS::Topic' +}); + +const bucketNotificationTarget = { + type: BucketNotificationTargetType.Topic, + arn: topic.ref +}; + +bucket.onEvent(EventType.ObjectCreated, { bucketNotificationTarget }, 'images/*', '*.jpg'); + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/s3/test/test.notifications.ts b/packages/@aws-cdk/s3/test/test.notifications.ts new file mode 100644 index 0000000000000..3906785a5c388 --- /dev/null +++ b/packages/@aws-cdk/s3/test/test.notifications.ts @@ -0,0 +1,160 @@ +import { expect } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/core'; +import { Arn } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Bucket, BucketNotificationTargetType, EventType, IBucketNotificationTarget } from '../lib'; + +export = { + 'subscription types'(test: Test) { + const stack = new Stack(); + + const bucket = new Bucket(stack, 'TestBucket'); + + const queueTarget: IBucketNotificationTarget = { + bucketNotificationTarget: { + type: BucketNotificationTargetType.Queue, + arn: new Arn('arn:aws:sqs:...') + } + }; + + const lambdaTarget: IBucketNotificationTarget = { + bucketNotificationTarget: { + type: BucketNotificationTargetType.Lambda, + arn: new Arn('arn:aws:lambda:...') + } + }; + + const topicTarget: IBucketNotificationTarget = { + bucketNotificationTarget: { + type: BucketNotificationTargetType.Topic, + arn: new Arn('arn:aws:sns:...') + } + }; + + bucket.onEvent(EventType.ObjectCreated, queueTarget); + bucket.onEvent(EventType.ObjectCreated, topicTarget); + bucket.onEvent(EventType.ObjectCreated, lambdaTarget); + + expect(stack).toMatch({ + Resources: { + TestBucket560B80BC: { + Type: "AWS::S3::Bucket", + Properties: { + NotificationConfiguration: { + LambdaConfigurations: [ + { + Event: "s3:ObjectCreated:*", + Function: "arn:aws:lambda:..." + } + ], + QueueConfigurations: [ + { + Event: "s3:ObjectCreated:*", + Queue: "arn:aws:sqs:..." + } + ], + TopicConfigurations: [ + { + Event: "s3:ObjectCreated:*", + Topic: "arn:aws:sns:..." + } + ] + } + } + } + } + }); + + test.done(); + }, + + 'multiple subscriptions of the same type'(test: Test) { + const stack = new Stack(); + + const bucket = new Bucket(stack, 'TestBucket'); + + bucket.onEvent(EventType.ObjectRemovedDelete, { + bucketNotificationTarget: { + type: BucketNotificationTargetType.Queue, + arn: new Arn('arn:aws:sqs:...:queue1') + } + }); + + bucket.onEvent(EventType.ObjectRemovedDelete, { + bucketNotificationTarget: { + type: BucketNotificationTargetType.Queue, + arn: new Arn('arn:aws:sqs:...:queue2') + } + }); + + expect(stack).toMatch({ + Resources: { + TestBucket560B80BC: { + Type: "AWS::S3::Bucket", + Properties: { + NotificationConfiguration: { + QueueConfigurations: [ + { + Event: "s3:ObjectRemoved:Delete", + Queue: "arn:aws:sqs:...:queue1" + }, + { + Event: "s3:ObjectRemoved:Delete", + Queue: "arn:aws:sqs:...:queue2" + } + ] + } + } + } + } + }); + test.done(); + }, + + 'prefix/suffix filters'(test: Test) { + const stack = new Stack(); + + const bucket = new Bucket(stack, 'TestBucket'); + + const bucketNotificationTarget = { + type: BucketNotificationTargetType.Queue, + arn: new Arn('arn:aws:sqs:...') + }; + + bucket.onEvent(EventType.ObjectRemovedDelete, { bucketNotificationTarget }, 'images/*', '*.jpg'); + + expect(stack).toMatch({ + Resources: { + TestBucket560B80BC: { + Type: "AWS::S3::Bucket", + Properties: { + NotificationConfiguration: { + QueueConfigurations: [ + { + Event: "s3:ObjectRemoved:Delete", + Filter: { + S3Key: { + Rules: [ + { + Name: "prefix", + Value: "images/" + }, + { + Name: "suffix", + Value: ".jpg" + } + ] + } + }, + Queue: "arn:aws:sqs:..." + } + ] + } + } + } + } + }); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/sns/lib/topic-ref.ts b/packages/@aws-cdk/sns/lib/topic-ref.ts index de3825e461f00..cc76e7b56e50e 100644 --- a/packages/@aws-cdk/sns/lib/topic-ref.ts +++ b/packages/@aws-cdk/sns/lib/topic-ref.ts @@ -2,6 +2,8 @@ import { Arn, Construct, Output, PolicyStatement, ServicePrincipal } from '@aws- import { EventRuleTarget, IEventRuleTarget } from '@aws-cdk/events'; import { IIdentityResource } from '@aws-cdk/iam'; import { LambdaRef } from '@aws-cdk/lambda'; +import { s3 } from '@aws-cdk/resources'; +import { BucketNotificationTarget, BucketNotificationTargetType, IBucketNotificationTarget } from '@aws-cdk/s3'; import { QueueRef } from '@aws-cdk/sqs'; import { TopicPolicy } from './policy'; import { Subscription, SubscriptionProtocol } from './subscription'; @@ -14,7 +16,7 @@ export class TopicArn extends Arn { } /** * Either a new or imported Topic */ -export abstract class TopicRef extends Construct implements IEventRuleTarget { +export abstract class TopicRef extends Construct implements IEventRuleTarget, IBucketNotificationTarget { /** * Import a Topic defined elsewhere */ @@ -39,6 +41,12 @@ export abstract class TopicRef extends Construct implements IEventRuleTarget { */ private eventRuleTargetPolicyAdded = false; + /** + * Indicates if the resource policy which allows S3 buckets to publish + * notifications for events has been added to the policy. + */ + private bucketNotificationTargetPolicyAdded = new Set(); + /** * Export this Topic */ @@ -216,6 +224,21 @@ export abstract class TopicRef extends Construct implements IEventRuleTarget { arn: this.topicArn, }; } + + public bucketNotificationTarget(bucketArn: s3.BucketArn): BucketNotificationTarget { + if (!this.bucketNotificationTargetPolicyAdded) { + this.addToResourcePolicy(new PolicyStatement() + .addAction('sns:Publish') + .addPrincipal(new ServicePrincipal('s3.amazonaws.com')) + .addResource(this.topicArn) + .addCondition('ArnLike', { 'aws:SourceArn': bucketArn })); + } + + return { + type: BucketNotificationTargetType.Topic, + arn: this.topicArn + }; + } } /** diff --git a/packages/@aws-cdk/sns/package.json b/packages/@aws-cdk/sns/package.json index b24959aea954a..42b83dae4bc45 100644 --- a/packages/@aws-cdk/sns/package.json +++ b/packages/@aws-cdk/sns/package.json @@ -46,6 +46,7 @@ "@aws-cdk/iam": "^0.7.2-beta", "@aws-cdk/lambda": "^0.7.2-beta", "@aws-cdk/resources": "^0.7.2-beta", - "@aws-cdk/sqs": "^0.7.2-beta" + "@aws-cdk/sqs": "^0.7.2-beta", + "@aws-cdk/s3": "^0.7.2-beta" } } diff --git a/packages/@aws-cdk/sns/test/integ.bucket-notification.expected.json b/packages/@aws-cdk/sns/test/integ.bucket-notification.expected.json new file mode 100644 index 0000000000000..5f97fd49456a2 --- /dev/null +++ b/packages/@aws-cdk/sns/test/integ.bucket-notification.expected.json @@ -0,0 +1,32 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "TopicConfigurations": [ + { + "Event": "s3:ObjectCreated:*", + "Filter": { + "S3Key": { + "Rules": [ + { + "Name": "prefix", + "Value": "files/" + } + ] + } + }, + "Topic": { + "Ref": "MyTopic86869434" + } + } + ] + } + } + }, + "MyTopic86869434": { + "Type": "AWS::SNS::Topic" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/sns/test/integ.bucket-notification.ts b/packages/@aws-cdk/sns/test/integ.bucket-notification.ts new file mode 100644 index 0000000000000..0df2d321d2413 --- /dev/null +++ b/packages/@aws-cdk/sns/test/integ.bucket-notification.ts @@ -0,0 +1,14 @@ +import { App, Stack } from '@aws-cdk/core'; +import { Bucket, EventType } from '@aws-cdk/s3'; +import { Topic } from '../lib'; + +const app = new App(process.argv); + +const stack = new Stack(app, 'aws-cdk-sns-bucket-notifications'); + +const bucket = new Bucket(stack, 'MyBucket'); +const topic = new Topic(stack, 'MyTopic'); + +bucket.onEvent(EventType.ObjectCreated, topic, 'files/*'); + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/sns/test/integ.bucket-notifications.expected.json b/packages/@aws-cdk/sns/test/integ.bucket-notifications.expected.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/@aws-cdk/sns/test/integ.bucket-notifications.expected.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/@aws-cdk/sns/test/test.sns.ts b/packages/@aws-cdk/sns/test/test.sns.ts index 5a077efff5dfc..5038869b38307 100644 --- a/packages/@aws-cdk/sns/test/test.sns.ts +++ b/packages/@aws-cdk/sns/test/test.sns.ts @@ -3,6 +3,7 @@ import { ArnPrincipal, PolicyStatement, resolve, Stack } from '@aws-cdk/core'; import { EventRule } from '@aws-cdk/events'; import { User } from '@aws-cdk/iam'; import { InlineJavaScriptLambda } from '@aws-cdk/lambda'; +import { Bucket, EventType } from '@aws-cdk/s3'; import { Queue } from '@aws-cdk/sqs'; import { Test } from 'nodeunit'; import { Topic } from '../lib'; @@ -649,6 +650,39 @@ export = { } }); + test.done(); + }, + + 'topic can be used as bucket notification targets'(test: Test) { + const stack = new Stack(); + const topic = new Topic(stack, 'MyTopic'); + const bucket = new Bucket(stack, 'MyBucket'); + + bucket.onEvent(EventType.ObjectCreated, topic); + + expect(stack).toMatch({ + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic" + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "TopicConfigurations": [ + { + "Event": "s3:ObjectCreated:*", + "Topic": { + "Ref": "MyTopic86869434" + } + } + ] + } + } + } + } + }); + test.done(); } };