From 35cde313726debfad1cc0d80bd5864c1f810c1c0 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 6 Jun 2019 22:33:45 +0200 Subject: [PATCH 01/11] feat(sns): add support for subscription filter policy --- packages/@aws-cdk/aws-sns/README.md | 20 +++ packages/@aws-cdk/aws-sns/lib/index.ts | 1 + .../aws-sns/lib/subscription-filter-policy.ts | 150 ++++++++++++++++++ packages/@aws-cdk/aws-sns/lib/subscription.ts | 9 ++ packages/@aws-cdk/aws-sns/lib/topic-base.ts | 39 +++-- .../test/integ.sns-lambda.expected.json | 31 +++- .../@aws-cdk/aws-sns/test/integ.sns-lambda.ts | 7 +- packages/@aws-cdk/aws-sns/test/test.sns.ts | 42 +++++ .../test/test.subscription-filter-policy.ts | 76 +++++++++ 9 files changed, 362 insertions(+), 13 deletions(-) create mode 100644 packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts create mode 100644 packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 9b1270f84fbc3..aaf8643b22db0 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -42,6 +42,26 @@ Subscribe a queue to the topic: Note that subscriptions of queues in different accounts need to be manually confirmed by reading the initial message from the queue and visiting the link found in it. +#### Filter policy +A filter policy can be specified when subscribing an endpoint to a topic. + +Example with a Lambda subscription: +```ts +const myTopic = new sns.Topic(this, 'MyTopic'); +const fn = new lambda.Function(this, 'Function', ...); + +// Lambda should receive only message matching the following conditions on attributes: +// color: 'red' or 'orange' or begins with 'bl' +// size: anything but 'small' or 'medium' +// price: between 100 and 200 or greater than 300 +const filterPolicy = new sns.SubscriptionFilterPolicy(); +filterPolicy.addStringFilter('color').whitelist('red', 'orange').matchPrefixes('bl'); +filterPolicy.addStringFilter('size').blacklist('small', 'medium'); +filterPolicy.addNumericFilter('price').between(100, 200).greaterThan(300); + +topic.subscribeLambda(fn, filterPolicy); +``` + ### CloudWatch Event Rule Target SNS topics can be used as targets for CloudWatch event rules. diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index 46e1f3418aff5..c7624f487773b 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -2,6 +2,7 @@ export * from './policy'; export * from './topic'; export * from './topic-base'; export * from './subscription'; +export * from './subscription-filter-policy'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; diff --git a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts new file mode 100644 index 0000000000000..664befc629226 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts @@ -0,0 +1,150 @@ +abstract class Filter { + /** + * The conditions of the filter. + * Conditions are `OR`ed. + */ + public readonly conditions: any[] = []; +} + +/** + * Filter for a string attribute. + */ +export class StringFilter extends Filter { + /** + * Match one or more values. + * Can be chained with other conditions. + */ + public whitelist(...values: string[]) { + this.conditions.push(...values); + return this; + } + + /** + * Match any value that doesn't include any of the specified values. + * Can be chained with other conditions. + */ + public blacklist(...values: string[]) { + this.conditions.push({ 'anything-but': values }); + return this; + } + + /** + * Matches values that begins with the specified prefixes. + * Can be chained with other conditions. + */ + public matchPrefixes(...prefixes: string[]) { + this.conditions.push(...prefixes.map(p => ({ prefix: p }))); + return this; + } +} + +export class NumericFilter extends Filter { + /** + * Match one or more values. + * Can be chained with other conditions. + */ + public whitelist(...values: number[]) { + this.conditions.push(...values.map(v => ({ numeric: ['=', v] }))); + return this; + } + + /** + * Match values that are greater than the specified value. + * Can be chained with other conditions. + */ + public greaterThan(value: number) { + this.conditions.push({ numeric: ['>', value] }); + return this; + } + + /** + * Match values that are greater than or equal to the specified value. + * Can be chained with other conditions. + */ + public greaterThanOrEqualTo(value: number) { + this.conditions.push({ numeric: ['>=', value] }); + return this; + } + + /** + * Match values that are less than the specified value. + * Can be chained with other conditions. + */ + public lessThan(value: number) { + this.conditions.push({ numeric: ['<', value] }); + return this; + } + + /** + * Match values that are less than or equal to the specified value. + * Can be chained with other conditions. + */ + public lessThanOrEqualTo(value: number) { + this.conditions.push({ numeric: ['<=', value] }); + return this; + } + + /** + * Match values that are between the specified values. + * Can be chained with other conditions. + */ + public between(start: number, stop: number) { + this.conditions.push({ numeric: ['>=', start, '<=', stop ]}); + return this; + } + + /** + * Match values that are strictly between the specified values. + * Can be chained with other conditions. + */ + public betweenStrict(start: number, stop: number) { + this.conditions.push({ numeric: ['>', start, '<', stop ]}); + return this; + } +} + +/** + * A SNS subscription filter policy. + */ +export class SubscriptionFilterPolicy { + private readonly policy: { [name: string]: any[] } = {}; + + /** + * Add a filter on a string attribute. + * + * @param name the attribute name + */ + public addStringFilter(name: string): StringFilter { + const filter = new StringFilter(); + this.policy[name] = filter.conditions; + return filter; + } + + /** + * Add a filter on a numeric attribute. + * + * @param name the attribute name + */ + public addNumericFilter(name: string): NumericFilter { + const filter = new NumericFilter(); + this.policy[name] = filter.conditions; + return filter; + } + + /** + * Renders the policy. + */ + public render() { + if (Object.keys(this.policy).length > 5) { + throw new Error('A filter policy can have a maximum of 5 attribute names.'); + } + + let total = 1; + Object.values(this.policy).forEach(filter => { total *= filter.length; }); + if (total > 100) { + throw new Error(`The total combination of values (${total}) must not exceed 100.`); + } + + return this.policy; + } +} diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 184227aac4b90..5c7948825158c 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -1,5 +1,6 @@ import { Construct, Resource } from '@aws-cdk/cdk'; import { CfnSubscription } from './sns.generated'; +import { SubscriptionFilterPolicy } from './subscription-filter-policy'; import { ITopic } from './topic-base'; /** @@ -31,6 +32,13 @@ export interface SubscriptionProps { * @default false */ readonly rawMessageDelivery?: boolean; + + /** + * The filter policy. + * + * @default all messages are delivered + */ + readonly filterPolicy?: SubscriptionFilterPolicy; } /** @@ -52,6 +60,7 @@ export class Subscription extends Resource { protocol: props.protocol, topicArn: props.topic.topicArn, rawMessageDelivery: props.rawMessageDelivery, + filterPolicy: props.filterPolicy && props.filterPolicy.render(), }); } diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index 7316fae6599a2..95b9fee4917d3 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -5,6 +5,7 @@ import cdk = require('@aws-cdk/cdk'); import { IResource, Resource } from '@aws-cdk/cdk'; import { TopicPolicy } from './policy'; import { Subscription, SubscriptionProtocol } from './subscription'; +import { SubscriptionFilterPolicy } from './subscription-filter-policy'; export interface ITopic extends IResource { /** @@ -20,7 +21,11 @@ export interface ITopic extends IResource { /** * Subscribe some endpoint to this topic */ - subscribe(name: string, endpoint: string, protocol: SubscriptionProtocol, rawMessageDelivery?: boolean): Subscription; + subscribe(name: string, + endpoint: string, + protocol: SubscriptionProtocol, + rawMessageDelivery?: boolean, + filterPolicy?: SubscriptionFilterPolicy): Subscription; /** * Defines a subscription from this SNS topic to an SQS queue. @@ -31,7 +36,7 @@ export interface ITopic extends IResource { * @param queue The target queue * @param rawMessageDelivery Enable raw message delivery */ - subscribeQueue(queue: sqs.IQueue, rawMessageDelivery?: boolean): Subscription; + subscribeQueue(queue: sqs.IQueue, rawMessageDelivery?: boolean, filterPolicy?: SubscriptionFilterPolicy): Subscription; /** * Defines a subscription from this SNS Topic to a Lambda function. @@ -41,7 +46,7 @@ export interface ITopic extends IResource { * * @param lambdaFunction The Lambda function to invoke */ - subscribeLambda(lambdaFunction: lambda.IFunction): Subscription; + subscribeLambda(lambdaFunction: lambda.IFunction, filterPolicy?: SubscriptionFilterPolicy): Subscription; /** * Defines a subscription from this SNS topic to an email address. @@ -50,7 +55,7 @@ export interface ITopic extends IResource { * @param emailAddress The email address to use. * @param options Options to use for email subscription */ - subscribeEmail(name: string, emailAddress: string, options?: EmailSubscriptionOptions): Subscription; + subscribeEmail(name: string, emailAddress: string, options?: EmailSubscriptionOptions, filterPolicy?: SubscriptionFilterPolicy): Subscription; /** * Defines a subscription from this SNS topic to an http:// or https:// URL. @@ -59,7 +64,7 @@ export interface ITopic extends IResource { * @param url The URL to invoke * @param rawMessageDelivery Enable raw message delivery */ - subscribeUrl(name: string, url: string, rawMessageDelivery?: boolean): Subscription; + subscribeUrl(name: string, url: string, rawMessageDelivery?: boolean, filterPolicy?: SubscriptionFilterPolicy): Subscription; /** * Adds a statement to the IAM resource policy associated with this topic. @@ -96,12 +101,17 @@ export abstract class TopicBase extends Resource implements ITopic { /** * Subscribe some endpoint to this topic */ - public subscribe(name: string, endpoint: string, protocol: SubscriptionProtocol, rawMessageDelivery?: boolean): Subscription { + public subscribe(name: string, + endpoint: string, + protocol: SubscriptionProtocol, + rawMessageDelivery?: boolean, + filterPolicy?: SubscriptionFilterPolicy): Subscription { return new Subscription(this, name, { topic: this, endpoint, protocol, rawMessageDelivery, + filterPolicy, }); } @@ -114,7 +124,7 @@ export abstract class TopicBase extends Resource implements ITopic { * @param queue The target queue * @param rawMessageDelivery Enable raw message delivery */ - public subscribeQueue(queue: sqs.IQueue, rawMessageDelivery?: boolean): Subscription { + public subscribeQueue(queue: sqs.IQueue, rawMessageDelivery?: boolean, filterPolicy?: SubscriptionFilterPolicy): Subscription { if (!cdk.Construct.isConstruct(queue)) { throw new Error(`The supplied Queue object must be an instance of Construct`); } @@ -132,6 +142,7 @@ export abstract class TopicBase extends Resource implements ITopic { endpoint: queue.queueArn, protocol: SubscriptionProtocol.Sqs, rawMessageDelivery, + filterPolicy, }); // add a statement to the queue resource policy which allows this topic @@ -153,7 +164,7 @@ export abstract class TopicBase extends Resource implements ITopic { * * @param lambdaFunction The Lambda function to invoke */ - public subscribeLambda(lambdaFunction: lambda.IFunction): Subscription { + public subscribeLambda(lambdaFunction: lambda.IFunction, filterPolicy?: SubscriptionFilterPolicy): Subscription { if (!cdk.Construct.isConstruct(lambdaFunction)) { throw new Error(`The supplied lambda Function object must be an instance of Construct`); } @@ -170,6 +181,7 @@ export abstract class TopicBase extends Resource implements ITopic { topic: this, endpoint: lambdaFunction.functionArn, protocol: SubscriptionProtocol.Lambda, + filterPolicy, }); lambdaFunction.addPermission(this.node.id, { @@ -187,13 +199,17 @@ export abstract class TopicBase extends Resource implements ITopic { * @param emailAddress The email address to use. * @param options Options for the email delivery format. */ - public subscribeEmail(name: string, emailAddress: string, options?: EmailSubscriptionOptions): Subscription { + public subscribeEmail(name: string, + emailAddress: string, + options?: EmailSubscriptionOptions, + filterPolicy?: SubscriptionFilterPolicy): Subscription { const protocol = (options && options.json ? SubscriptionProtocol.EmailJson : SubscriptionProtocol.Email); return new Subscription(this, name, { topic: this, endpoint: emailAddress, - protocol + protocol, + filterPolicy }); } @@ -204,7 +220,7 @@ export abstract class TopicBase extends Resource implements ITopic { * @param url The URL to invoke * @param rawMessageDelivery Enable raw message delivery */ - public subscribeUrl(name: string, url: string, rawMessageDelivery?: boolean): Subscription { + public subscribeUrl(name: string, url: string, rawMessageDelivery?: boolean, filterPolicy?: SubscriptionFilterPolicy): Subscription { if (!url.startsWith('http://') && !url.startsWith('https://')) { throw new Error('URL must start with either http:// or https://'); } @@ -216,6 +232,7 @@ export abstract class TopicBase extends Resource implements ITopic { endpoint: url, protocol, rawMessageDelivery, + filterPolicy, }); } diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json index 62614184e66a6..e13a7b69a324b 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json @@ -75,6 +75,35 @@ "Echo11F3FB29", "Arn" ] + }, + "FilterPolicy": { + "color": [ + "red", + { + "prefix": "bl" + }, + { + "prefix": "ye" + } + ], + "size": [ + { + "anything-but": [ + "small", + "medium" + ] + } + ], + "price": [ + { + "numeric": [ + ">=", + 100, + "<=", + 200 + ] + } + ] } } }, @@ -95,4 +124,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts index d925481d46ca5..520d233e5faee 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts @@ -14,7 +14,12 @@ class SnsToSqs extends cdk.Stack { code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) }); - topic.subscribeLambda(fction); + const filterPolicy = new sns.SubscriptionFilterPolicy(); + filterPolicy.addStringFilter('color').whitelist('red').matchPrefixes('bl', 'ye'); + filterPolicy.addStringFilter('size').blacklist('small', 'medium'); + filterPolicy.addNumericFilter('price').between(100, 200); + + topic.subscribeLambda(fction, filterPolicy); } } diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index bf7a5363fdf60..b61c6b8a7c0b8 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -370,6 +370,48 @@ export = { test.done(); }, + 'lambda subscription with filter policy'(test: Test) { + const stack = new cdk.Stack(); + + const topic = new sns.Topic(stack, 'MyTopic', { + topicName: 'topicName', + displayName: 'displayName' + }); + + const fction = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.NodeJS810, + handler: 'index.handler', + code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') + }); + + const filterPolicy = new sns.SubscriptionFilterPolicy(); + filterPolicy.addStringFilter('color').whitelist('red'); + filterPolicy.addNumericFilter('price').between(100, 200); + + topic.subscribeLambda(fction, filterPolicy); + + expect(stack).to(haveResource('AWS::SNS::Subscription', { + Endpoint: { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn' + ] + }, + Protocol: 'lambda', + TopicArn: { + Ref: 'MyTopic86869434' + }, + FilterPolicy: { + color: ['red'], + price: [ + { numeric: ['>=', 100, '<=', 200] } + ] + }, + })); + + test.done(); + }, + 'email subscription'(test: Test) { const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts new file mode 100644 index 0000000000000..1d0957ee765d9 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts @@ -0,0 +1,76 @@ +import { Test } from 'nodeunit'; +import sns = require('../lib'); + +export = { + 'create a filter policy'(test: Test) { + // GIVEN + const policy = new sns.SubscriptionFilterPolicy(); + + // WHEN + policy + .addStringFilter('color') + .whitelist('red', 'green') + .blacklist('white', 'orange') + .matchPrefixes('bl', 'ye'); + policy + .addNumericFilter('price') + .whitelist(100, 200) + .between(300, 350) + .greaterThan(500) + .lessThan(1000) + .betweenStrict(2000, 3000) + .greaterThanOrEqualTo(1000) + .lessThanOrEqualTo(-2); + + // THEN + test.deepEqual(policy.render(), { + color: [ + 'red', + 'green', + {'anything-but': ['white', 'orange']}, + { prefix: 'bl'}, + { prefix: 'ye'} + ], + price: [ + { numeric: ['=', 100] }, + { numeric: ['=', 200] }, + { numeric: ['>=', 300, '<=', 350] }, + { numeric: ['>', 500] }, + { numeric: ['<', 1000] }, + { numeric: ['>', 2000, '<', 3000] }, + { numeric: ['>=', 1000] }, + { numeric: ['<=', -2] }, + ] + }); + test.done(); + }, + + 'throws with more than 5 attributes'(test: Test) { + // GIVEN + const policy = new sns.SubscriptionFilterPolicy(); + + // WHEN + [...Array(6).keys()].forEach((k) => policy.addStringFilter(`${k}`)); + + // THEN + test.throws(() => policy.render(), /5 attribute names/); + test.done(); + }, + + 'throws with more than 100 conditions'(test: Test) { + // GIVEN + const policy = new sns.SubscriptionFilterPolicy(); + + // WHEN + const first = policy.addNumericFilter('first'); + const second = policy.addNumericFilter('second'); + const third = policy.addNumericFilter('third'); + first.whitelist(...Array.from(Array(2).keys())); + second.whitelist(...Array.from(Array(10).keys())); + third.whitelist(...Array.from(Array(6).keys())); + + // THEN + test.throws(() => policy.render(), /\(120\) must not exceed 100/); + test.done(); + } +}; From 6a67fad495de293c89446fb84d7f2695483e4bb0 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 11 Jun 2019 16:47:40 +0200 Subject: [PATCH 02/11] move filter policy prop to aws-sns-subscriptions --- .../aws-sns-subscriptions/lib/email.ts | 15 +++-- .../aws-sns-subscriptions/lib/index.ts | 1 + .../aws-sns-subscriptions/lib/lambda.ts | 28 ++++---- .../@aws-cdk/aws-sns-subscriptions/lib/sqs.ts | 27 ++++---- .../aws-sns-subscriptions/lib/subscription.ts | 10 +++ .../@aws-cdk/aws-sns-subscriptions/lib/url.ts | 13 ++-- .../test/integ.sns-lambda.expected.json | 32 ++++----- .../test/integ.sns-sqs.lit.expected.json | 8 +-- .../aws-sns-subscriptions/test/subs.test.ts | 66 +++++++++++++++++-- packages/@aws-cdk/aws-sns/lib/subscriber.ts | 11 +++- packages/@aws-cdk/aws-sns/lib/subscription.ts | 20 +++--- packages/@aws-cdk/aws-sns/lib/topic-base.ts | 14 +++- packages/@aws-cdk/aws-sns/package.json | 3 +- 13 files changed, 167 insertions(+), 81 deletions(-) create mode 100644 packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts index b678b9e102cfd..8a715d5cc3ce8 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts @@ -1,10 +1,10 @@ import sns = require('@aws-cdk/aws-sns'); -import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Options for email subscriptions. */ -export interface EmailSubscriptionProps { +export interface EmailSubscriptionProps extends SubscriptionProps { /** * Indicates if the full notification JSON should be sent to the email * address or just the message text. @@ -23,11 +23,12 @@ export class EmailSubscription implements sns.ITopicSubscription { constructor(private readonly emailAddress: string, private readonly props: EmailSubscriptionProps = {}) { } - public bind(scope: Construct, topic: sns.ITopic): void { - new sns.Subscription(scope, this.emailAddress, { - topic, + public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { + return { + id: this.emailAddress, endpoint: this.emailAddress, - protocol: this.props.json ? sns.SubscriptionProtocol.EmailJson : sns.SubscriptionProtocol.Email - }); + protocol: this.props.json ? sns.SubscriptionProtocol.EmailJson : sns.SubscriptionProtocol.Email, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts index c63c355ff3aa1..a6646dfa2e0af 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts @@ -1,3 +1,4 @@ +export * from './subscription'; export * from './email'; export * from './lambda'; export * from './sqs'; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index fd8d67c281247..e9a32804fefe4 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -2,37 +2,35 @@ import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import sns = require('@aws-cdk/aws-sns'); import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; +export interface LambdaSubscriptionProps extends SubscriptionProps { + +} /** * Use a Lambda function as a subscription target */ export class LambdaSubscription implements sns.ITopicSubscription { - constructor(private readonly fn: lambda.IFunction) { + constructor(private readonly fn: lambda.IFunction, private readonly props: LambdaSubscriptionProps = {}) { } - public bind(_scope: Construct, topic: sns.ITopic): void { + public bind(topic: sns.ITopic): sns.TopicSubscriptionConfig { // Create subscription under *consuming* construct to make sure it ends up // in the correct stack in cases of cross-stack subscriptions. if (!Construct.isConstruct(this.fn)) { throw new Error(`The supplied lambda Function object must be an instance of Construct`); } - // we use the target name as the subscription's. there's no meaning to - // subscribing the same queue twice on the same topic. - const subscriptionName = topic.node.id + 'Subscription'; - if (this.fn.node.tryFindChild(subscriptionName)) { - throw new Error(`A subscription between the topic ${topic.node.id} and the lambda ${this.fn.id} already exists`); - } - - new sns.Subscription(this.fn, subscriptionName, { - topic, - endpoint: this.fn.functionArn, - protocol: sns.SubscriptionProtocol.Lambda, - }); - this.fn.addPermission(topic.node.id, { sourceArn: topic.topicArn, principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); + + return { + id: this.fn.node.id, + endpoint: this.fn.functionArn, + protocol: sns.SubscriptionProtocol.Lambda, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 2e427276d4fd5..6f0f4210adcd5 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -2,11 +2,12 @@ import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); import sqs = require('@aws-cdk/aws-sqs'); import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Properties for an SQS subscription */ -export interface SqsSubscriptionProps { +export interface SqsSubscriptionProps extends SubscriptionProps { /** * The message to the queue is the same as it was sent to the topic * @@ -24,27 +25,13 @@ export class SqsSubscription implements sns.ITopicSubscription { constructor(private readonly queue: sqs.IQueue, private readonly props: SqsSubscriptionProps = {}) { } - public bind(_scope: Construct, topic: sns.ITopic): void { + public bind(topic: sns.ITopic): sns.TopicSubscriptionConfig { // Create subscription under *consuming* construct to make sure it ends up // in the correct stack in cases of cross-stack subscriptions. if (!Construct.isConstruct(this.queue)) { throw new Error(`The supplied Queue object must be an instance of Construct`); } - // we use the queue name as the subscription's. there's no meaning to - // subscribing the same queue twice on the same topic. - const subscriptionName = topic.node.id + 'Subscription'; - if (this.queue.node.tryFindChild(subscriptionName)) { - throw new Error(`A subscription between the topic ${topic.node.id} and the queue ${this.queue.node.id} already exists`); - } - - new sns.Subscription(this.queue, subscriptionName, { - topic, - endpoint: this.queue.queueArn, - protocol: sns.SubscriptionProtocol.Sqs, - rawMessageDelivery: this.props.rawMessageDelivery, - }); - // add a statement to the queue resource policy which allows this topic // to send messages to the queue. this.queue.addToResourcePolicy(new iam.PolicyStatement() @@ -52,5 +39,13 @@ export class SqsSubscription implements sns.ITopicSubscription { .addAction('sqs:SendMessage') .addServicePrincipal('sns.amazonaws.com') .setCondition('ArnEquals', { 'aws:SourceArn': topic.topicArn })); + + return { + id: this.queue.node.id, + endpoint: this.queue.queueArn, + protocol: sns.SubscriptionProtocol.Sqs, + rawMessageDelivery: this.props.rawMessageDelivery, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts new file mode 100644 index 0000000000000..c5a41075fe0d2 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts @@ -0,0 +1,10 @@ +import sns = require('@aws-cdk/aws-sns'); + +export interface SubscriptionProps { + /** + * The filter policy. + * + * @default all messages are delivered + */ + readonly filterPolicy?: sns.SubscriptionFilterPolicy; +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts index 91046dc4e2407..9c836be659ab7 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts @@ -1,10 +1,10 @@ import sns = require('@aws-cdk/aws-sns'); -import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Options for URL subscriptions. */ -export interface UrlSubscriptionProps { +export interface UrlSubscriptionProps extends SubscriptionProps { /** * The message to the queue is the same as it was sent to the topic * @@ -29,12 +29,13 @@ export class UrlSubscription implements sns.ITopicSubscription { } } - public bind(scope: Construct, topic: sns.ITopic): void { - new sns.Subscription(scope, this.url, { - topic, + public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { + return { + id: this.url, endpoint: this.url, protocol: this.url.startsWith('https:') ? sns.SubscriptionProtocol.Https : sns.SubscriptionProtocol.Http, rawMessageDelivery: this.props.rawMessageDelivery, - }); + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json index 62614184e66a6..fd63eaa4cd2ac 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json @@ -3,6 +3,21 @@ "MyTopic86869434": { "Type": "AWS::SNS::Topic" }, + "MyTopicEchoD1E0EE5C": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "MyTopic86869434" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + } + } + }, "EchoServiceRoleBE28060B": { "Type": "AWS::IAM::Role", "Properties": { @@ -63,21 +78,6 @@ "EchoServiceRoleBE28060B" ] }, - "EchoMyTopicSubscriptionA634C6D7": { - "Type": "AWS::SNS::Subscription", - "Properties": { - "Protocol": "lambda", - "TopicArn": { - "Ref": "MyTopic86869434" - }, - "Endpoint": { - "Fn::GetAtt": [ - "Echo11F3FB29", - "Arn" - ] - } - } - }, "EchoMyTopicF6EBB45F": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -95,4 +95,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json index 868a13aed58da..31a0f99f6514a 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json @@ -3,10 +3,7 @@ "MyTopic86869434": { "Type": "AWS::SNS::Topic" }, - "MyQueueE6CA6235": { - "Type": "AWS::SQS::Queue" - }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", @@ -21,6 +18,9 @@ } } }, + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue" + }, "MyQueuePolicy6BBEDDAC": { "Type": "AWS::SQS::QueuePolicy", "Properties": { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index da1c73d17733e..b89919f952d48 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -85,7 +85,7 @@ test('queue subscription', () => { "TopicName": "topicName" } }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -180,7 +180,7 @@ test('lambda subscription', () => { "TopicName": "topicName" } }, - "MyFuncMyTopicSubscription708A6535": { + "MyTopicMyFunc853BC1D3": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -300,7 +300,7 @@ test('multiple subscriptions', () => { "TopicName": "topicName" } }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -315,7 +315,7 @@ test('multiple subscriptions', () => { } } }, - "MyFuncMyTopicSubscription708A6535": { + "MyTopicMyFunc853BC1D3": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -437,3 +437,61 @@ test('multiple subscriptions', () => { } }); }); + +test('throws with mutliple subscriptions of the same subscriber', () => { + const queue = new sqs.Queue(stack, 'MyQueue'); + + topic.addSubscription(new subs.SqsSubscription(queue)); + + expect(() => topic.addSubscription(new subs.SqsSubscription(queue))) + .toThrowError(/subscriber MyQueue already exists/); +}); + +test('with filter policy', () => { + const fction = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.NodeJS810, + handler: 'index.handler', + code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') + }); + + const filterPolicy = new sns.SubscriptionFilterPolicy(); + filterPolicy.addStringFilter('color').whitelist('red').matchPrefixes('bl', 'ye'); + filterPolicy.addStringFilter('size').blacklist('small', 'medium'); + filterPolicy.addNumericFilter('price').between(100, 200); + + topic.addSubscription(new subs.LambdaSubscription(fction, { + filterPolicy + })); + + expect(stack).toHaveResource('AWS::SNS::Subscription', { + "FilterPolicy": { + "color": [ + "red", + { + "prefix": "bl" + }, + { + "prefix": "ye" + } + ], + "size": [ + { + "anything-but": [ + "small", + "medium" + ] + } + ], + "price": [ + { + "numeric": [ + ">=", + 100, + "<=", + 200 + ] + } + ] + } + }); +}); diff --git a/packages/@aws-cdk/aws-sns/lib/subscriber.ts b/packages/@aws-cdk/aws-sns/lib/subscriber.ts index 82cd69d1bbc6d..9f50ddc3d6b24 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscriber.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscriber.ts @@ -1,9 +1,16 @@ -import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionOptions } from './subscription'; import { ITopic } from './topic-base'; +export interface TopicSubscriptionConfig extends SubscriptionOptions { + /** + * The id of the construct that is being subscribed to the topic. + */ + readonly id: string; +} + /** * Topic subscription */ export interface ITopicSubscription { - bind(scope: Construct, topic: ITopic): void; + bind(topic: ITopic): TopicSubscriptionConfig; } diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 5c7948825158c..3bf40245cb745 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -4,9 +4,9 @@ import { SubscriptionFilterPolicy } from './subscription-filter-policy'; import { ITopic } from './topic-base'; /** - * Properties for creating a new subscription + * Options for creating a new subscription */ -export interface SubscriptionProps { +export interface SubscriptionOptions { /** * What type of subscription to add. */ @@ -19,11 +19,6 @@ export interface SubscriptionProps { */ readonly endpoint: string; - /** - * The topic to subscribe to. - */ - readonly topic: ITopic; - /** * true if raw message delivery is enabled for the subscription. Raw messages are free of JSON formatting and can be * sent to HTTP/S and Amazon SQS endpoints. For more information, see GetSubscriptionAttributes in the Amazon Simple @@ -40,11 +35,20 @@ export interface SubscriptionProps { */ readonly filterPolicy?: SubscriptionFilterPolicy; } +/** + * Properties for creating a new subscription + */ +export interface SubscriptionProps extends SubscriptionOptions { + /** + * The topic to subscribe to. + */ + readonly topic: ITopic; +} /** * A new subscription. * - * Prefer to use the `ITopic.subscribeXxx()` methods to creating instances of + * Prefer to use the `ITopic.addSubscription()` methods to create instances of * this class. */ export class Subscription extends Resource { diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index 7c6c4c842e4fb..08f984a98fc7e 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -2,6 +2,7 @@ import iam = require('@aws-cdk/aws-iam'); import { IResource, Resource } from '@aws-cdk/cdk'; import { TopicPolicy } from './policy'; import { ITopicSubscription } from './subscriber'; +import { Subscription } from './subscription'; export interface ITopic extends IResource { /** @@ -55,7 +56,18 @@ export abstract class TopicBase extends Resource implements ITopic { * Subscribe some endpoint to this topic */ public addSubscription(subscription: ITopicSubscription) { - subscription.bind(this, this); + const subscriptionConfig = subscription.bind(this); + + // We use the subscriber's id as the construct id. There's no meaning + // to subscribing the same subscriber twice on the same topic. + if (this.node.tryFindChild(subscriptionConfig.id)) { + throw new Error(`A subscription between the topic ${this.node.id} and the subscriber ${subscriptionConfig.id} already exists`); + } + + new Subscription(this, subscriptionConfig.id, { + topic: this, + ...subscriptionConfig, + }); } /** diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 7b1e40a7ab768..dc83051c37060 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -91,8 +91,7 @@ }, "awslint": { "exclude": [ - "construct-base-is-private:@aws-cdk/aws-sns.TopicBase", - "integ-return-type:@aws-cdk/aws-sns.ITopicSubscription.bind" + "construct-base-is-private:@aws-cdk/aws-sns.TopicBase" ] }, "stability": "experimental" From e57bfb330965fd334e9a60227bfffe0eae837a92 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 11 Jun 2019 16:52:25 +0200 Subject: [PATCH 03/11] JSDoc --- packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts | 3 +++ packages/@aws-cdk/aws-sns/lib/subscriber.ts | 3 +++ packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index e9a32804fefe4..130f105236c3b 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -4,6 +4,9 @@ import sns = require('@aws-cdk/aws-sns'); import { Construct } from '@aws-cdk/cdk'; import { SubscriptionProps } from './subscription'; +/** + * Properties for a Lambda subscription + */ export interface LambdaSubscriptionProps extends SubscriptionProps { } diff --git a/packages/@aws-cdk/aws-sns/lib/subscriber.ts b/packages/@aws-cdk/aws-sns/lib/subscriber.ts index 9f50ddc3d6b24..5c45817be3489 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscriber.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscriber.ts @@ -1,6 +1,9 @@ import { SubscriptionOptions } from './subscription'; import { ITopic } from './topic-base'; +/** + * Subscription configuration + */ export interface TopicSubscriptionConfig extends SubscriptionOptions { /** * The id of the construct that is being subscribed to the topic. diff --git a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts index 664befc629226..2a8f526cce027 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts @@ -38,6 +38,9 @@ export class StringFilter extends Filter { } } +/** + * Filter for a number attribute. + */ export class NumericFilter extends Filter { /** * Match one or more values. From 2a378920483e5d4181dce69e22bb2981e0066b76 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 13 Jun 2019 18:29:17 +0200 Subject: [PATCH 04/11] no fluent interfaces --- .../test/integ.sns-lambda.expected.json | 29 +++ .../test/integ.sns-lambda.ts | 15 +- .../aws-sns-subscriptions/test/subs.test.ts | 20 +- packages/@aws-cdk/aws-sns/README.md | 19 +- .../aws-sns/lib/subscription-filter-policy.ts | 190 ++++++++++-------- packages/@aws-cdk/aws-sns/lib/subscription.ts | 2 +- .../test/test.subscription-filter-policy.ts | 72 +++---- 7 files changed, 209 insertions(+), 138 deletions(-) diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json index fd63eaa4cd2ac..1e09e24b3054a 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json @@ -15,6 +15,35 @@ "Echo11F3FB29", "Arn" ] + }, + "FilterPolicy": { + "color": [ + "red", + { + "prefix": "bl" + }, + { + "prefix": "ye" + } + ], + "size": [ + { + "anything-but": [ + "small", + "medium" + ] + } + ], + "price": [ + { + "numeric": [ + ">=", + 100, + "<=", + 200 + ] + } + ] } } }, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts index 4a6efc3f5699d..43a9d3293039a 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts @@ -15,7 +15,20 @@ class SnsToSqs extends cdk.Stack { code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) }); - topic.addSubscription(new subs.LambdaSubscription(fction)); + topic.addSubscription(new subs.LambdaSubscription(fction, { + filterPolicy: new sns.SubscriptionFilterPolicy({ + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red'], + matchPrefixes: ['bl', 'ye'], + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 } + }) + }) + })); } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 8feef82daf915..5cfa24ebcdde6 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -449,18 +449,24 @@ test('throws with mutliple subscriptions of the same subscriber', () => { test('with filter policy', () => { const fction = new lambda.Function(stack, 'MyFunc', { - runtime: lambda.Runtime.NodeJS810, + runtime: lambda.Runtime.Nodejs810, handler: 'index.handler', code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') }); - const filterPolicy = new sns.SubscriptionFilterPolicy(); - filterPolicy.addStringFilter('color').whitelist('red').matchPrefixes('bl', 'ye'); - filterPolicy.addStringFilter('size').blacklist('small', 'medium'); - filterPolicy.addNumericFilter('price').between(100, 200); - topic.addSubscription(new subs.LambdaSubscription(fction, { - filterPolicy + filterPolicy: new sns.SubscriptionFilterPolicy({ + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red'], + matchPrefixes: ['bl', 'ye'], + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 } + }) + }) })); expect(stack).toHaveResource('AWS::SNS::Subscription', { diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 1830b87dbfc78..10b5aee17913d 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -60,12 +60,21 @@ const fn = new lambda.Function(this, 'Function', ...); // color: 'red' or 'orange' or begins with 'bl' // size: anything but 'small' or 'medium' // price: between 100 and 200 or greater than 300 -const filterPolicy = new sns.SubscriptionFilterPolicy(); -filterPolicy.addStringFilter('color').whitelist('red', 'orange').matchPrefixes('bl'); -filterPolicy.addStringFilter('size').blacklist('small', 'medium'); -filterPolicy.addNumericFilter('price').between(100, 200).greaterThan(300); +const filterPolicy = new sns.SubscriptionFilterPolicy({ + color: sns.SubscriptionFilter.stringFilter({ + whiteliste: ['red', 'orange'], + matchPrefixes: ['bl'] + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: [100, 200], + greaterThan: 300 + }) +}); -topic.subscribeLambda(fn, filterPolicy); +topic.subscribeLambda(new subs.LambdaSubscription(fn, { filterPolicy })); ``` ### CloudWatch Event Rule Target diff --git a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts index 2a8f526cce027..58b7f2f1a895b 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts @@ -1,153 +1,167 @@ -abstract class Filter { +/** + * A SNS subscription filter policy. + */ +export class SubscriptionFilterPolicy { /** - * The conditions of the filter. - * Conditions are `OR`ed. + * The filter policy. */ - public readonly conditions: any[] = []; + public readonly policy: { [attribute: string]: any[] }; + + constructor(attributesMap: { [attribute: string]: SubscriptionFilter }) { + if (Object.keys(attributesMap).length > 5) { + throw new Error('A filter policy can have a maximum of 5 attribute names.'); + } + + this.policy = Object.entries(attributesMap) + .reduce( + (acc, [k, v]) => ({ ...acc, [k]: v.conditions }), + {} + ); + + let total = 1; + Object.values(this.policy).forEach(filter => { total *= filter.length; }); + if (total > 100) { + throw new Error(`The total combination of values (${total}) must not exceed 100.`); + } + } } /** - * Filter for a string attribute. + * Conditions that can be applied to string attributes. */ -export class StringFilter extends Filter { +export interface StringConditions { /** * Match one or more values. - * Can be chained with other conditions. */ - public whitelist(...values: string[]) { - this.conditions.push(...values); - return this; - } + readonly whitelist?: string[]; /** * Match any value that doesn't include any of the specified values. - * Can be chained with other conditions. */ - public blacklist(...values: string[]) { - this.conditions.push({ 'anything-but': values }); - return this; - } + readonly blacklist?: string[]; /** * Matches values that begins with the specified prefixes. - * Can be chained with other conditions. */ - public matchPrefixes(...prefixes: string[]) { - this.conditions.push(...prefixes.map(p => ({ prefix: p }))); - return this; - } + readonly matchPrefixes?: string[]; +} + +/** + * Between condition for a numeric attribute. + */ +export interface BetweenCondition { + /** + * The start value. + */ + readonly start: number; + + /** + * The stop value. + */ + readonly stop: number; } /** - * Filter for a number attribute. + * Conditions that can be applied to numeric attributes. */ -export class NumericFilter extends Filter { +export interface NumericConditions { /** * Match one or more values. - * Can be chained with other conditions. */ - public whitelist(...values: number[]) { - this.conditions.push(...values.map(v => ({ numeric: ['=', v] }))); - return this; - } + readonly whitelist?: number[]; /** * Match values that are greater than the specified value. - * Can be chained with other conditions. */ - public greaterThan(value: number) { - this.conditions.push({ numeric: ['>', value] }); - return this; - } + readonly greaterThan?: number; /** * Match values that are greater than or equal to the specified value. - * Can be chained with other conditions. */ - public greaterThanOrEqualTo(value: number) { - this.conditions.push({ numeric: ['>=', value] }); - return this; - } + readonly greaterThanOrEqualTo?: number; /** * Match values that are less than the specified value. - * Can be chained with other conditions. */ - public lessThan(value: number) { - this.conditions.push({ numeric: ['<', value] }); - return this; - } + readonly lessThan?: number; /** * Match values that are less than or equal to the specified value. - * Can be chained with other conditions. */ - public lessThanOrEqualTo(value: number) { - this.conditions.push({ numeric: ['<=', value] }); - return this; - } + readonly lessThanOrEqualTo?: number; /** * Match values that are between the specified values. - * Can be chained with other conditions. */ - public between(start: number, stop: number) { - this.conditions.push({ numeric: ['>=', start, '<=', stop ]}); - return this; - } + readonly between?: BetweenCondition; /** * Match values that are strictly between the specified values. - * Can be chained with other conditions. */ - public betweenStrict(start: number, stop: number) { - this.conditions.push({ numeric: ['>', start, '<', stop ]}); - return this; - } + readonly betweenStrict?: BetweenCondition; } /** - * A SNS subscription filter policy. + * A subscription filter for an attribute. */ -export class SubscriptionFilterPolicy { - private readonly policy: { [name: string]: any[] } = {}; - +export class SubscriptionFilter { /** - * Add a filter on a string attribute. - * - * @param name the attribute name + * Returns a subscription filter for a string attribute. */ - public addStringFilter(name: string): StringFilter { - const filter = new StringFilter(); - this.policy[name] = filter.conditions; - return filter; - } + public static stringFilter(stringConditions: StringConditions) { + const conditions = []; - /** - * Add a filter on a numeric attribute. - * - * @param name the attribute name - */ - public addNumericFilter(name: string): NumericFilter { - const filter = new NumericFilter(); - this.policy[name] = filter.conditions; - return filter; + if (stringConditions.whitelist) { + conditions.push(...stringConditions.whitelist); + } + + if (stringConditions.blacklist) { + conditions.push({ 'anything-but': stringConditions.blacklist }); + } + + if (stringConditions.matchPrefixes) { + conditions.push(...stringConditions.matchPrefixes.map(p => ({ prefix: p }))); + } + + return new SubscriptionFilter(conditions); } /** - * Renders the policy. + * Returns a subscription filter for a numeric attribute. */ - public render() { - if (Object.keys(this.policy).length > 5) { - throw new Error('A filter policy can have a maximum of 5 attribute names.'); + public static numericFilter(numericConditions: NumericConditions) { + const conditions = []; + + if (numericConditions.whitelist) { + conditions.push(...numericConditions.whitelist.map(v => ({ numeric: ['=', v] }))); } - let total = 1; - Object.values(this.policy).forEach(filter => { total *= filter.length; }); - if (total > 100) { - throw new Error(`The total combination of values (${total}) must not exceed 100.`); + if (numericConditions.greaterThan) { + conditions.push({ numeric: ['>', numericConditions.greaterThan] }); + } + + if (numericConditions.greaterThanOrEqualTo) { + conditions.push({ numeric: ['>=', numericConditions.greaterThanOrEqualTo] }); + } + + if (numericConditions.lessThan) { + conditions.push({ numeric: ['<', numericConditions.lessThan] }); } - return this.policy; + if (numericConditions.lessThanOrEqualTo) { + conditions.push({ numeric: ['<=', numericConditions.lessThanOrEqualTo] }); + } + + if (numericConditions.between) { + conditions.push({ numeric: ['>=', numericConditions.between.start, '<=', numericConditions.between.stop ]}); + } + + if (numericConditions.betweenStrict) { + conditions.push({ numeric: ['>', numericConditions.betweenStrict.start, '<', numericConditions.betweenStrict.stop ]}); + } + + return new SubscriptionFilter(conditions); } + + constructor(public readonly conditions: any[] = []) {} } diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 3bf40245cb745..08a613c19dfab 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -64,7 +64,7 @@ export class Subscription extends Resource { protocol: props.protocol, topicArn: props.topic.topicArn, rawMessageDelivery: props.rawMessageDelivery, - filterPolicy: props.filterPolicy && props.filterPolicy.render(), + filterPolicy: props.filterPolicy && props.filterPolicy.policy, }); } diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts index 1d0957ee765d9..eaffcd4073dbb 100644 --- a/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts +++ b/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts @@ -4,26 +4,25 @@ import sns = require('../lib'); export = { 'create a filter policy'(test: Test) { // GIVEN - const policy = new sns.SubscriptionFilterPolicy(); - - // WHEN - policy - .addStringFilter('color') - .whitelist('red', 'green') - .blacklist('white', 'orange') - .matchPrefixes('bl', 'ye'); - policy - .addNumericFilter('price') - .whitelist(100, 200) - .between(300, 350) - .greaterThan(500) - .lessThan(1000) - .betweenStrict(2000, 3000) - .greaterThanOrEqualTo(1000) - .lessThanOrEqualTo(-2); + const policy = new sns.SubscriptionFilterPolicy({ + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red', 'green'], + blacklist: ['white', 'orange'], + matchPrefixes: ['bl', 'ye'], + }), + price: sns.SubscriptionFilter.numericFilter({ + whitelist: [100, 200], + between: { start: 300, stop: 350 }, + greaterThan: 500, + lessThan: 1000, + betweenStrict: { start: 2000, stop: 3000 }, + greaterThanOrEqualTo: 1000, + lessThanOrEqualTo: -2, + }) + }); // THEN - test.deepEqual(policy.render(), { + test.deepEqual(policy.policy, { color: [ 'red', 'green', @@ -34,12 +33,12 @@ export = { price: [ { numeric: ['=', 100] }, { numeric: ['=', 200] }, - { numeric: ['>=', 300, '<=', 350] }, { numeric: ['>', 500] }, - { numeric: ['<', 1000] }, - { numeric: ['>', 2000, '<', 3000] }, { numeric: ['>=', 1000] }, + { numeric: ['<', 1000] }, { numeric: ['<=', -2] }, + { numeric: ['>=', 300, '<=', 350] }, + { numeric: ['>', 2000, '<', 3000] }, ] }); test.done(); @@ -47,30 +46,31 @@ export = { 'throws with more than 5 attributes'(test: Test) { // GIVEN - const policy = new sns.SubscriptionFilterPolicy(); - - // WHEN - [...Array(6).keys()].forEach((k) => policy.addStringFilter(`${k}`)); + const cond = { conditions: [] }; + const attributesMap = { + a: cond, + b: cond, + c: cond, + d: cond, + e: cond, + f: cond, + }; // THEN - test.throws(() => policy.render(), /5 attribute names/); + test.throws(() => new sns.SubscriptionFilterPolicy(attributesMap), /5 attribute names/); test.done(); }, 'throws with more than 100 conditions'(test: Test) { // GIVEN - const policy = new sns.SubscriptionFilterPolicy(); - - // WHEN - const first = policy.addNumericFilter('first'); - const second = policy.addNumericFilter('second'); - const third = policy.addNumericFilter('third'); - first.whitelist(...Array.from(Array(2).keys())); - second.whitelist(...Array.from(Array(10).keys())); - third.whitelist(...Array.from(Array(6).keys())); + const attributesMap = { + a: { conditions: [...Array.from(Array(2).keys())] }, + b: { conditions: [...Array.from(Array(10).keys())] }, + c: { conditions: [...Array.from(Array(6).keys())] }, + }; // THEN - test.throws(() => policy.render(), /\(120\) must not exceed 100/); + test.throws(() => new sns.SubscriptionFilterPolicy(attributesMap), /\(120\) must not exceed 100/); test.done(); } }; From c1c86942e279b4ed441cff10104e6146431f2307 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 14 Jun 2019 10:19:36 +0200 Subject: [PATCH 05/11] integ tests and decdk snapshot --- ...integ.scheduled-ecs-task.lit.expected.json | 2 +- .../test/ec2/integ.lb-awsvpc-nw.expected.json | 2 +- .../test/ec2/integ.lb-bridge-nw.expected.json | 2 +- .../test/ec2/integ.sd-awsvpc-nw.expected.json | 4 +- .../test/ec2/integ.sd-bridge-nw.expected.json | 4 +- .../integ.project-events.expected.json | 2 +- .../integ.event-ec2-task.lit.expected.json | 4 +- .../integ.sns-event-rule-target.expected.json | 4 +- .../test/integ.sns.expected.json | 2 +- .../test/integ.ec2-task.expected.json | 4 +- .../test/__snapshots__/synth.test.js.snap | 38 +++++++++---------- 11 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json index df6ad15d15295..7e066eb457a1d 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json @@ -507,7 +507,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index 45625b2d4a15e..9396d5a54ad4a 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -663,7 +663,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index 4316141bd1952..e18ffcb164d17 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -684,7 +684,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json index 82b202d4f6551..8088e5b087f2e 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json @@ -663,7 +663,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -938,4 +938,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json index c8770a3740c09..7e744faf1ed5a 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json @@ -663,7 +663,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -902,4 +902,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index a87767b5ef881..ef94515d4b613 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -327,7 +327,7 @@ "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue" }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json index 70ec436edefaf..474ded4acdf51 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json @@ -507,7 +507,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -1190,4 +1190,4 @@ "Description": "Artifact hash for asset \"aws-ecs-integ-ecs/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json index e7113875c4cea..d3a4e7b129c02 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json @@ -57,7 +57,7 @@ "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue" }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", @@ -108,4 +108,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json index 6a50e98575458..1134471c0d6b7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json @@ -60,7 +60,7 @@ "FServiceRole3AC82EE1" ] }, - "FTSubscription775EAF05": { + "TF2453034D": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index f8d6494414d5b..8420fd0031452 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -315,7 +315,7 @@ "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" ] }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionA4A8D57E": { + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionE74B772A": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -1032,4 +1032,4 @@ "Description": "Artifact hash for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index 4ff4e5d64b382..8609471339f4f 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -1213,21 +1213,6 @@ Object { }, "Type": "AWS::Lambda::Permission", }, - "HelloWorldFunctionMyTopicSubscription20D3FA87": Object { - "Properties": Object { - "Endpoint": Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionB2AB6E79", - "Arn", - ], - }, - "Protocol": "lambda", - "TopicArn": Object { - "Ref": "MyTopic86869434", - }, - }, - "Type": "AWS::SNS::Subscription", - }, "HelloWorldFunctionServiceRole8E0BD458": Object { "Properties": Object { "AssumeRolePolicyDocument": Object { @@ -1307,6 +1292,21 @@ Object { "MyTopic86869434": Object { "Type": "AWS::SNS::Topic", }, + "MyTopicHelloWorldFunction831B106E": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], + }, + "Protocol": "lambda", + "TopicArn": Object { + "Ref": "MyTopic86869434", + }, + }, + "Type": "AWS::SNS::Subscription", + }, "TableCD117FA1": Object { "Properties": Object { "AttributeDefinitions": Array [ @@ -1645,7 +1645,10 @@ Object { }, "Type": "AWS::Lambda::Permission", }, - "LambdaTopicSubscriptionF6CC623D": Object { + "TopicBFC7AF6E": Object { + "Type": "AWS::SNS::Topic", + }, + "TopicLambda3DD31D45": Object { "Properties": Object { "Endpoint": Object { "Fn::GetAtt": Array [ @@ -1660,9 +1663,6 @@ Object { }, "Type": "AWS::SNS::Subscription", }, - "TopicBFC7AF6E": Object { - "Type": "AWS::SNS::Topic", - }, }, } `; From 29cc31bffcf82a11c7015e473da5e64464f2cdbe Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Jun 2019 15:19:40 +0200 Subject: [PATCH 06/11] add integ test instead of updating it --- .../test/integ.sns-lambda.expected.json | 91 +++++++++++++++++++ .../test/integ.sns-lambda.ts | 14 ++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json index 1e09e24b3054a..c2b5ff331efd6 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json @@ -15,6 +15,21 @@ "Echo11F3FB29", "Arn" ] + } + } + }, + "MyTopicFiltered55457D11": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "MyTopic86869434" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Filtered186C0D0A", + "Arn" + ] }, "FilterPolicy": { "color": [ @@ -122,6 +137,82 @@ "Ref": "MyTopic86869434" } } + }, + "FilteredServiceRole16D9DDC1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Filtered186C0D0A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FilteredServiceRole16D9DDC1", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "FilteredServiceRole16D9DDC1" + ] + }, + "FilteredMyTopic804BCBC3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Filtered186C0D0A", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "MyTopic86869434" + } + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts index 43a9d3293039a..675a65517b26d 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts @@ -3,7 +3,7 @@ import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); import subs = require('../lib'); -class SnsToSqs extends cdk.Stack { +class SnsToLambda extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); @@ -15,7 +15,15 @@ class SnsToSqs extends cdk.Stack { code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) }); - topic.addSubscription(new subs.LambdaSubscription(fction, { + topic.addSubscription(new subs.LambdaSubscription(fction)); + + const fctionFiltered = new lambda.Function(this, 'Filtered', { + handler: 'index.handler', + runtime: lambda.Runtime.Nodejs810, + code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) + }); + + topic.addSubscription(new subs.LambdaSubscription(fctionFiltered, { filterPolicy: new sns.SubscriptionFilterPolicy({ color: sns.SubscriptionFilter.stringFilter({ whitelist: ['red'], @@ -34,7 +42,7 @@ class SnsToSqs extends cdk.Stack { const app = new cdk.App(); -new SnsToSqs(app, 'aws-cdk-sns-lambda'); +new SnsToLambda(app, 'aws-cdk-sns-lambda'); app.synth(); From 7ede17c4ce69ba6277cae48573e35c1697cad8c7 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Jun 2019 15:53:37 +0200 Subject: [PATCH 07/11] remove SubscriptionFilterPolicy class --- .../aws-sns-subscriptions/lib/subscription.ts | 4 +- .../test/integ.sns-lambda.ts | 4 +- .../aws-sns-subscriptions/test/subs.test.ts | 4 +- packages/@aws-cdk/aws-sns/README.md | 30 ++-- packages/@aws-cdk/aws-sns/lib/index.ts | 2 +- ...ilter-policy.ts => subscription-filter.ts} | 28 ---- packages/@aws-cdk/aws-sns/lib/subscription.ts | 28 +++- .../test/test.subscription-filter-policy.ts | 76 ---------- .../aws-sns/test/test.subscription.ts | 139 ++++++++++++++++++ 9 files changed, 185 insertions(+), 130 deletions(-) rename packages/@aws-cdk/aws-sns/lib/{subscription-filter-policy.ts => subscription-filter.ts} (81%) delete mode 100644 packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts create mode 100644 packages/@aws-cdk/aws-sns/test/test.subscription.ts diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts index c5a41075fe0d2..603f54c835583 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts @@ -4,7 +4,7 @@ export interface SubscriptionProps { /** * The filter policy. * - * @default all messages are delivered + * @default - all messages are delivered */ - readonly filterPolicy?: sns.SubscriptionFilterPolicy; + readonly filterPolicy?: { [attribute: string]: sns.SubscriptionFilter }; } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts index 675a65517b26d..bd4e992df9c24 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts @@ -24,7 +24,7 @@ class SnsToLambda extends cdk.Stack { }); topic.addSubscription(new subs.LambdaSubscription(fctionFiltered, { - filterPolicy: new sns.SubscriptionFilterPolicy({ + filterPolicy: { color: sns.SubscriptionFilter.stringFilter({ whitelist: ['red'], matchPrefixes: ['bl', 'ye'], @@ -35,7 +35,7 @@ class SnsToLambda extends cdk.Stack { price: sns.SubscriptionFilter.numericFilter({ between: { start: 100, stop: 200 } }) - }) + } })); } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 5cfa24ebcdde6..e92bcd75f30cd 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -455,7 +455,7 @@ test('with filter policy', () => { }); topic.addSubscription(new subs.LambdaSubscription(fction, { - filterPolicy: new sns.SubscriptionFilterPolicy({ + filterPolicy: { color: sns.SubscriptionFilter.stringFilter({ whitelist: ['red'], matchPrefixes: ['bl', 'ye'], @@ -466,7 +466,7 @@ test('with filter policy', () => { price: sns.SubscriptionFilter.numericFilter({ between: { start: 100, stop: 200 } }) - }) + } })); expect(stack).toHaveResource('AWS::SNS::Subscription', { diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 10b5aee17913d..398d3b64fdbef 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -60,21 +60,21 @@ const fn = new lambda.Function(this, 'Function', ...); // color: 'red' or 'orange' or begins with 'bl' // size: anything but 'small' or 'medium' // price: between 100 and 200 or greater than 300 -const filterPolicy = new sns.SubscriptionFilterPolicy({ - color: sns.SubscriptionFilter.stringFilter({ - whiteliste: ['red', 'orange'], - matchPrefixes: ['bl'] - }), - size: sns.SubscriptionFilter.stringFilter({ - blacklist: ['small', 'medium'], - }), - price: sns.SubscriptionFilter.numericFilter({ - between: [100, 200], - greaterThan: 300 - }) -}); - -topic.subscribeLambda(new subs.LambdaSubscription(fn, { filterPolicy })); +topic.subscribeLambda(new subs.LambdaSubscription(fn, { + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whiteliste: ['red', 'orange'], + matchPrefixes: ['bl'] + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: [100, 200], + greaterThan: 300 + }) + } +})); ``` ### CloudWatch Event Rule Target diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index 09c3af52cd2bf..cdcb67084921e 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -3,7 +3,7 @@ export * from './topic'; export * from './topic-base'; export * from './subscription'; export * from './subscriber'; -export * from './subscription-filter-policy'; +export * from './subscription-filter'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; diff --git a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/lib/subscription-filter.ts similarity index 81% rename from packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts rename to packages/@aws-cdk/aws-sns/lib/subscription-filter.ts index 58b7f2f1a895b..188e843cd1fc7 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription-filter-policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription-filter.ts @@ -1,31 +1,3 @@ -/** - * A SNS subscription filter policy. - */ -export class SubscriptionFilterPolicy { - /** - * The filter policy. - */ - public readonly policy: { [attribute: string]: any[] }; - - constructor(attributesMap: { [attribute: string]: SubscriptionFilter }) { - if (Object.keys(attributesMap).length > 5) { - throw new Error('A filter policy can have a maximum of 5 attribute names.'); - } - - this.policy = Object.entries(attributesMap) - .reduce( - (acc, [k, v]) => ({ ...acc, [k]: v.conditions }), - {} - ); - - let total = 1; - Object.values(this.policy).forEach(filter => { total *= filter.length; }); - if (total > 100) { - throw new Error(`The total combination of values (${total}) must not exceed 100.`); - } - } -} - /** * Conditions that can be applied to string attributes. */ diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 08a613c19dfab..f68686ab761bc 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -1,6 +1,6 @@ import { Construct, Resource } from '@aws-cdk/cdk'; import { CfnSubscription } from './sns.generated'; -import { SubscriptionFilterPolicy } from './subscription-filter-policy'; +import { SubscriptionFilter } from './subscription-filter'; import { ITopic } from './topic-base'; /** @@ -31,9 +31,9 @@ export interface SubscriptionOptions { /** * The filter policy. * - * @default all messages are delivered + * @default - all messages are delivered */ - readonly filterPolicy?: SubscriptionFilterPolicy; + readonly filterPolicy?: { [attribute: string]: SubscriptionFilter }; } /** * Properties for creating a new subscription @@ -52,6 +52,8 @@ export interface SubscriptionProps extends SubscriptionOptions { * this class. */ export class Subscription extends Resource { + private readonly filterPolicy?: { [attribute: string]: any[] }; + constructor(scope: Construct, id: string, props: SubscriptionProps) { super(scope, id); @@ -59,12 +61,30 @@ export class Subscription extends Resource { throw new Error('Raw message delivery can only be enabled for HTTP/S and SQS subscriptions.'); } + if (props.filterPolicy) { + if (Object.keys(props.filterPolicy).length > 5) { + throw new Error('A filter policy can have a maximum of 5 attribute names.'); + } + + this.filterPolicy = Object.entries(props.filterPolicy) + .reduce( + (acc, [k, v]) => ({ ...acc, [k]: v.conditions }), + {} + ); + + let total = 1; + Object.values(this.filterPolicy).forEach(filter => { total *= filter.length; }); + if (total > 100) { + throw new Error(`The total combination of values (${total}) must not exceed 100.`); + } + } + new CfnSubscription(this, 'Resource', { endpoint: props.endpoint, protocol: props.protocol, topicArn: props.topic.topicArn, rawMessageDelivery: props.rawMessageDelivery, - filterPolicy: props.filterPolicy && props.filterPolicy.policy, + filterPolicy: this.filterPolicy, }); } diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts b/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts deleted file mode 100644 index eaffcd4073dbb..0000000000000 --- a/packages/@aws-cdk/aws-sns/test/test.subscription-filter-policy.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Test } from 'nodeunit'; -import sns = require('../lib'); - -export = { - 'create a filter policy'(test: Test) { - // GIVEN - const policy = new sns.SubscriptionFilterPolicy({ - color: sns.SubscriptionFilter.stringFilter({ - whitelist: ['red', 'green'], - blacklist: ['white', 'orange'], - matchPrefixes: ['bl', 'ye'], - }), - price: sns.SubscriptionFilter.numericFilter({ - whitelist: [100, 200], - between: { start: 300, stop: 350 }, - greaterThan: 500, - lessThan: 1000, - betweenStrict: { start: 2000, stop: 3000 }, - greaterThanOrEqualTo: 1000, - lessThanOrEqualTo: -2, - }) - }); - - // THEN - test.deepEqual(policy.policy, { - color: [ - 'red', - 'green', - {'anything-but': ['white', 'orange']}, - { prefix: 'bl'}, - { prefix: 'ye'} - ], - price: [ - { numeric: ['=', 100] }, - { numeric: ['=', 200] }, - { numeric: ['>', 500] }, - { numeric: ['>=', 1000] }, - { numeric: ['<', 1000] }, - { numeric: ['<=', -2] }, - { numeric: ['>=', 300, '<=', 350] }, - { numeric: ['>', 2000, '<', 3000] }, - ] - }); - test.done(); - }, - - 'throws with more than 5 attributes'(test: Test) { - // GIVEN - const cond = { conditions: [] }; - const attributesMap = { - a: cond, - b: cond, - c: cond, - d: cond, - e: cond, - f: cond, - }; - - // THEN - test.throws(() => new sns.SubscriptionFilterPolicy(attributesMap), /5 attribute names/); - test.done(); - }, - - 'throws with more than 100 conditions'(test: Test) { - // GIVEN - const attributesMap = { - a: { conditions: [...Array.from(Array(2).keys())] }, - b: { conditions: [...Array.from(Array(10).keys())] }, - c: { conditions: [...Array.from(Array(6).keys())] }, - }; - - // THEN - test.throws(() => new sns.SubscriptionFilterPolicy(attributesMap), /\(120\) must not exceed 100/); - test.done(); - } -}; diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription.ts b/packages/@aws-cdk/aws-sns/test/test.subscription.ts new file mode 100644 index 0000000000000..61ba64e839548 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/test/test.subscription.ts @@ -0,0 +1,139 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import sns = require('../lib'); + +export = { + 'create a subscription'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.Lambda, + topic + }); + + // THEN + expect(stack).to(haveResource('AWS::SNS::Subscription', { + Endpoint: 'endpoint', + Protocol: 'lambda', + TopicArn: { + Ref: 'TopicBFC7AF6E' + } + })); + test.done(); + }, + + 'with filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red', 'green'], + blacklist: ['white', 'orange'], + matchPrefixes: ['bl', 'ye'], + }), + price: sns.SubscriptionFilter.numericFilter({ + whitelist: [100, 200], + between: { start: 300, stop: 350 }, + greaterThan: 500, + lessThan: 1000, + betweenStrict: { start: 2000, stop: 3000 }, + greaterThanOrEqualTo: 1000, + lessThanOrEqualTo: -2, + }) + }, + protocol: sns.SubscriptionProtocol.Lambda, + topic + }); + + // THEN + expect(stack).to(haveResource('AWS::SNS::Subscription', { + FilterPolicy: { + color: [ + 'red', + 'green', + {'anything-but': ['white', 'orange']}, + { prefix: 'bl'}, + { prefix: 'ye'} + ], + price: [ + { numeric: ['=', 100] }, + { numeric: ['=', 200] }, + { numeric: ['>', 500] }, + { numeric: ['>=', 1000] }, + { numeric: ['<', 1000] }, + { numeric: ['<=', -2] }, + { numeric: ['>=', 300, '<=', 350] }, + { numeric: ['>', 2000, '<', 3000] }, + ] + }, + })); + test.done(); + }, + + 'throws with raw delivery for protocol other than http, https or sqs'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.Lambda, + topic, + rawMessageDelivery: true + }), /Raw message delivery/); + test.done(); + }, + + 'throws with more than 5 attributes in a filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + const cond = { conditions: [] }; + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.Lambda, + topic, + filterPolicy: { + a: cond, + b: cond, + c: cond, + d: cond, + e: cond, + f: cond, + }, + }), /5 attribute names/); + test.done(); + }, + + 'throws with more than 100 conditions in a filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.Lambda, + topic, + filterPolicy: { + a: { conditions: [...Array.from(Array(2).keys())] }, + b: { conditions: [...Array.from(Array(10).keys())] }, + c: { conditions: [...Array.from(Array(6).keys())] }, + }, + }), /\(120\) must not exceed 100/); + test.done(); + } +}; From 2f4427ef3cbc1287d0c679a210130eb106f9df80 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Jun 2019 15:59:49 +0200 Subject: [PATCH 08/11] subscriberId --- packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts | 2 +- packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts | 2 +- packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts | 2 +- packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts | 2 +- packages/@aws-cdk/aws-sns/lib/subscriber.ts | 5 +++-- packages/@aws-cdk/aws-sns/lib/topic-base.ts | 6 +++--- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts index 8a715d5cc3ce8..863c97137af7d 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts @@ -25,7 +25,7 @@ export class EmailSubscription implements sns.ITopicSubscription { public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { return { - id: this.emailAddress, + subscriberId: this.emailAddress, endpoint: this.emailAddress, protocol: this.props.json ? sns.SubscriptionProtocol.EmailJson : sns.SubscriptionProtocol.Email, filterPolicy: this.props.filterPolicy, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index 130f105236c3b..7b4f450f08f29 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -30,7 +30,7 @@ export class LambdaSubscription implements sns.ITopicSubscription { }); return { - id: this.fn.node.id, + subscriberId: this.fn.node.id, endpoint: this.fn.functionArn, protocol: sns.SubscriptionProtocol.Lambda, filterPolicy: this.props.filterPolicy, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 96622f47cb76d..6440728d00526 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -44,7 +44,7 @@ export class SqsSubscription implements sns.ITopicSubscription { })); return { - id: this.queue.node.id, + subscriberId: this.queue.node.id, endpoint: this.queue.queueArn, protocol: sns.SubscriptionProtocol.Sqs, rawMessageDelivery: this.props.rawMessageDelivery, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts index 9c836be659ab7..a14c62f5fb59e 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts @@ -31,7 +31,7 @@ export class UrlSubscription implements sns.ITopicSubscription { public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { return { - id: this.url, + subscriberId: this.url, endpoint: this.url, protocol: this.url.startsWith('https:') ? sns.SubscriptionProtocol.Https : sns.SubscriptionProtocol.Http, rawMessageDelivery: this.props.rawMessageDelivery, diff --git a/packages/@aws-cdk/aws-sns/lib/subscriber.ts b/packages/@aws-cdk/aws-sns/lib/subscriber.ts index 5c45817be3489..1077732ef8df9 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscriber.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscriber.ts @@ -6,9 +6,10 @@ import { ITopic } from './topic-base'; */ export interface TopicSubscriptionConfig extends SubscriptionOptions { /** - * The id of the construct that is being subscribed to the topic. + * The id of the subscriber. Will be used as the id for the subscription in + * the topic's scope. */ - readonly id: string; + readonly subscriberId: string; } /** diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index ed9604050cca8..cd76a274d10b4 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -60,11 +60,11 @@ export abstract class TopicBase extends Resource implements ITopic { // We use the subscriber's id as the construct id. There's no meaning // to subscribing the same subscriber twice on the same topic. - if (this.node.tryFindChild(subscriptionConfig.id)) { - throw new Error(`A subscription between the topic ${this.node.id} and the subscriber ${subscriptionConfig.id} already exists`); + if (this.node.tryFindChild(subscriptionConfig.subscriberId)) { + throw new Error(`A subscription between the topic ${this.node.id} and the subscriber ${subscriptionConfig.subscriberId} already exists`); } - new Subscription(this, subscriptionConfig.id, { + new Subscription(this, subscriptionConfig.subscriberId, { topic: this, ...subscriptionConfig, }); From 54ad0d4d917bc53f19f3b15d09686e98cb2f25df Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Jun 2019 16:04:41 +0200 Subject: [PATCH 09/11] typo --- packages/@aws-cdk/aws-sns/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 398d3b64fdbef..126d03c5d234e 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -63,7 +63,7 @@ const fn = new lambda.Function(this, 'Function', ...); topic.subscribeLambda(new subs.LambdaSubscription(fn, { filterPolicy: { color: sns.SubscriptionFilter.stringFilter({ - whiteliste: ['red', 'orange'], + whitelist: ['red', 'orange'], matchPrefixes: ['bl'] }), size: sns.SubscriptionFilter.stringFilter({ From 48271a53c7839640f3af2ef626d81525ee7691b2 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Jun 2019 17:11:24 +0200 Subject: [PATCH 10/11] typo in `between` in README --- packages/@aws-cdk/aws-sns/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 126d03c5d234e..2681ec394496b 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -70,7 +70,7 @@ topic.subscribeLambda(new subs.LambdaSubscription(fn, { blacklist: ['small', 'medium'], }), price: sns.SubscriptionFilter.numericFilter({ - between: [100, 200], + between: { start: 100, stop: 200 }, greaterThan: 300 }) } From 2ee217c6879d0e203385f57d8330b12a10dd1b71 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 20 Jun 2019 12:30:28 +0200 Subject: [PATCH 11/11] LAMBDA --- packages/@aws-cdk/aws-sns/test/test.subscription.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription.ts b/packages/@aws-cdk/aws-sns/test/test.subscription.ts index 61ba64e839548..6bf653ce95f35 100644 --- a/packages/@aws-cdk/aws-sns/test/test.subscription.ts +++ b/packages/@aws-cdk/aws-sns/test/test.subscription.ts @@ -12,7 +12,7 @@ export = { // WHEN new sns.Subscription(stack, 'Subscription', { endpoint: 'endpoint', - protocol: sns.SubscriptionProtocol.Lambda, + protocol: sns.SubscriptionProtocol.LAMBDA, topic }); @@ -51,7 +51,7 @@ export = { lessThanOrEqualTo: -2, }) }, - protocol: sns.SubscriptionProtocol.Lambda, + protocol: sns.SubscriptionProtocol.LAMBDA, topic }); @@ -88,7 +88,7 @@ export = { // THEN test.throws(() => new sns.Subscription(stack, 'Subscription', { endpoint: 'endpoint', - protocol: sns.SubscriptionProtocol.Lambda, + protocol: sns.SubscriptionProtocol.LAMBDA, topic, rawMessageDelivery: true }), /Raw message delivery/); @@ -104,7 +104,7 @@ export = { // THEN test.throws(() => new sns.Subscription(stack, 'Subscription', { endpoint: 'endpoint', - protocol: sns.SubscriptionProtocol.Lambda, + protocol: sns.SubscriptionProtocol.LAMBDA, topic, filterPolicy: { a: cond, @@ -126,7 +126,7 @@ export = { // THEN test.throws(() => new sns.Subscription(stack, 'Subscription', { endpoint: 'endpoint', - protocol: sns.SubscriptionProtocol.Lambda, + protocol: sns.SubscriptionProtocol.LAMBDA, topic, filterPolicy: { a: { conditions: [...Array.from(Array(2).keys())] },