UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

222 lines (203 loc) • 7.84 kB
'use strict'; const _ = require('lodash'); class AwsCompileSNSEvents { constructor(serverless, options) { this.serverless = serverless; this.provider = this.serverless.getProvider('aws'); this.options = options; this.hooks = { 'package:compileEvents': this.compileSNSEvents.bind(this), }; } invalidPropertyErrorMessage(functionName, property) { return [ `Missing or invalid ${property} property for sns event`, ` in function "${functionName}"`, ' The correct syntax is: sns: topic-name-or-arn', ' OR an object with ', ' arn and topicName OR', ' topicName and displayName.', ' Please check the docs for more info.', ].join(''); } isValidStackImport(variable) { if (Object.keys(variable).length !== 1) { return false; } if ( _.has(variable, 'Fn::ImportValue') && (_.has(variable, 'Fn::ImportValue.Fn::GetAtt') || _.has(variable, 'Fn::ImportValue.Ref')) ) { return false; } const intrinsicFunctions = ['Fn::ImportValue', 'Ref', 'Fn::GetAtt', 'Fn::Sub', 'Fn::Join']; return !!_.find(intrinsicFunctions, func => _.has(variable, func)); } compileSNSEvents() { const template = this.serverless.service.provider.compiledCloudFormationTemplate; this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { functionObj.events.forEach(event => { if (event.sns) { let topicArn; let topicName; let region; let displayName = ''; if (typeof event.sns === 'object') { if (event.sns.arn) { topicArn = event.sns.arn; if (typeof topicArn === 'object') { if (!this.isValidStackImport(topicArn)) { throw new this.serverless.classes.Error( this.invalidPropertyErrorMessage(functionName, 'arn') ); } } else if (typeof topicArn === 'string') { if (topicArn.indexOf('arn:') === 0) { const splitArn = topicArn.split(':'); topicName = splitArn[splitArn.length - 1]; if (splitArn[3] !== this.options.region) { region = splitArn[3]; } } else { throw new this.serverless.classes.Error( this.invalidPropertyErrorMessage(functionName, 'arn') ); } } else { throw new this.serverless.classes.Error( this.invalidPropertyErrorMessage(functionName, 'arn') ); } topicName = topicName || event.sns.topicName; if (!topicName || typeof topicName !== 'string') { throw new this.serverless.classes.Error( this.invalidPropertyErrorMessage(functionName, 'topicName') ); } } else { ['topicName', 'displayName'].forEach(property => { if (typeof event.sns[property] === 'string') { return; } throw new this.serverless.classes.Error( this.invalidPropertyErrorMessage(functionName, property) ); }); displayName = event.sns.displayName; topicName = event.sns.topicName; } } else if (typeof event.sns === 'string') { if (event.sns.indexOf('arn:') === 0) { topicArn = event.sns; const splitArn = topicArn.split(':'); topicName = splitArn[splitArn.length - 1]; } else { topicName = event.sns; } } else { const errorMessage = [ `SNS event of function ${functionName} is not an object nor a string`, ' The correct syntax is: sns: topic-name-or-arn', ' OR an object with ', ' arn and topicName OR', ' topicName and displayName.', ' Please check the docs for more info.', ].join(''); throw new this.serverless.classes.Error(errorMessage); } const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); const endpoint = { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }; const subscriptionLogicalId = this.provider.naming.getLambdaSnsSubscriptionLogicalId( functionName, topicName ); if (topicArn) { _.merge(template.Resources, { [subscriptionLogicalId]: { Type: 'AWS::SNS::Subscription', Properties: { TopicArn: topicArn, Protocol: 'lambda', Endpoint: endpoint, FilterPolicy: event.sns.filterPolicy, Region: region, }, }, }); } else { topicArn = { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, ':sns:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':', topicName, ], ], }; const topicLogicalId = this.provider.naming.getTopicLogicalId(topicName); const subscription = { Endpoint: endpoint, Protocol: 'lambda', }; if (!(topicLogicalId in template.Resources)) { _.merge(template.Resources, { [topicLogicalId]: { Type: 'AWS::SNS::Topic', Properties: { TopicName: topicName, DisplayName: displayName, }, }, }); } if (event.sns.filterPolicy) { _.merge(template.Resources, { [subscriptionLogicalId]: { Type: 'AWS::SNS::Subscription', Properties: _.merge(subscription, { TopicArn: { Ref: topicLogicalId, }, FilterPolicy: event.sns.filterPolicy, }), }, }); } else { if (!template.Resources[topicLogicalId].Properties.Subscription) { template.Resources[topicLogicalId].Properties.Subscription = []; } template.Resources[topicLogicalId].Properties.Subscription.push(subscription); } } const lambdaPermissionLogicalId = this.provider.naming.getLambdaSnsPermissionLogicalId( functionName, topicName ); _.merge(template.Resources, { [lambdaPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: endpoint, Action: 'lambda:InvokeFunction', Principal: 'sns.amazonaws.com', SourceArn: topicArn, }, }, }); } }); } }); } } module.exports = AwsCompileSNSEvents;