diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index aa7581653d5ba..58c7a2aceb16b 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Names, Stack } from '@aws-cdk/core'; +import { Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -36,6 +36,12 @@ export class LambdaSubscription implements sns.ITopicSubscription { principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); + // if the topic and function are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.fn.stack) { + this.fn.stack.addDependency(topic.stack); + } + return { subscriberScope: this.fn, subscriberId: topic.node.id, @@ -50,6 +56,14 @@ export class LambdaSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack. if (topic instanceof sns.Topic) { + if (topic.stack !== this.fn.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.fn.stack.region)) { + return topic.stack.region; + } + } return undefined; } return Stack.of(topic).parseArn(topic.topicArn).region; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 6cf89ebc53c60..8bbb77927381f 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Names, Stack } from '@aws-cdk/core'; +import { Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -61,6 +61,12 @@ export class SqsSubscription implements sns.ITopicSubscription { })); } + // if the topic and queue are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.queue.stack) { + this.queue.stack.addDependency(topic.stack); + } + return { subscriberScope: this.queue, subscriberId: Names.nodeUniqueId(topic.node), @@ -76,6 +82,14 @@ export class SqsSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack if (topic instanceof sns.Topic) { + if (topic.stack !== this.queue.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.queue.stack.region)) { + return topic.stack.region; + } + } return undefined; } return Stack.of(topic).parseArn(topic.topicArn).region; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json new file mode 100644 index 0000000000000..de5216b565954 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json @@ -0,0 +1,116 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "EchoServiceRoleBE28060B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Echo11F3FB29": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n /* eslint-disable no-console */\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + }, + "Role": { + "Fn::GetAtt": [ + "EchoServiceRoleBE28060B", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "EchoServiceRoleBE28060B" + ] + }, + "EchoAllowInvokeTopicStackMyTopicC43E67AF32CF6EFA": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "EchoMyTopic4CB8819E": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts new file mode 100644 index 0000000000000..cfec9592e3dba --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts @@ -0,0 +1,35 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const functionStack = new cdk.Stack(app, 'FunctionStack', { + env: { region: 'us-east-2' }, +}); +const fction = new lambda.Function(functionStack, 'Echo', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), +}); + +topic.addSubscription(new subs.LambdaSubscription(fction)); + +app.synth(); + +function handler(event: any, _context: any, callback: any) { + /* eslint-disable no-console */ + console.log('===================================================='); + console.log(JSON.stringify(event, undefined, 2)); + console.log('===================================================='); + return callback(undefined, event); +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json new file mode 100644 index 0000000000000..5bbffb5e31628 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json @@ -0,0 +1,90 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyQueuePolicy6BBEDDAC": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "MyQueueE6CA6235" + } + ] + } + }, + "MyQueueTopicStackMyTopicC43E67AFC8DC8B4A": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts new file mode 100644 index 0000000000000..ca53a70194e03 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts @@ -0,0 +1,25 @@ +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +/// !show +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const queueStack = new cdk.Stack(app, 'QueueStack', { + env: { region: 'us-east-2' }, +}); +const queue = new sqs.Queue(queueStack, 'MyQueue'); + +topic.addSubscription(new subs.SqsSubscription(queue)); +/// !hide + +app.synth(); 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 8be564b5a9188..671937a3ed01e 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -3,7 +3,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; +import { App, CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; import * as subs from '../lib'; /* eslint-disable quote-props */ @@ -308,6 +308,455 @@ test('queue subscription', () => { }); }); +test('queue subscription cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + +test('queue subscription cross region, env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + 'Outputs': { + 'ExportsOutputRefTopicBFC7AF6ECB4A357A': { + 'Value': { + 'Ref': 'TopicBFC7AF6E', + }, + 'Export': { + 'Name': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, topic env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, queue env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); test('queue subscription with user provided dlq', () => { const queue = new sqs.Queue(stack, 'MyQueue'); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { @@ -712,6 +1161,243 @@ test('lambda subscription', () => { }); }); +test('lambda subscription, cross region env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const lambdaStack = new Stack(app, 'LambdaStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'Service': 'lambda.amazonaws.com', + }, + }, + ], + 'Version': '2012-10-17', + }, + 'ManagedPolicyArns': [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('lambda subscription, cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const lambdaStack = new Stack(app, 'LambdaStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'Service': 'lambda.amazonaws.com', + }, + }, + ], + 'Version': '2012-10-17', + }, + 'ManagedPolicyArns': [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + test('email subscription', () => { topic.addSubscription(new subs.EmailSubscription('foo@bar.com'));