UNPKG

serverless-plugin-external-sns-events

Version:

Plugin for the SLS 1.x branch to provide better support for SNS events to your Lambda functions

169 lines (138 loc) 6.07 kB
'use strict'; var _ = require('underscore'), Class = require('class.extend'); module.exports = Class.extend({ init: function(serverless, opts) { this._serverless = serverless; this._opts = opts; this.provider = serverless ? serverless.getProvider('aws') : null; this.hooks = { 'deploy:compileEvents': this._loopEvents.bind(this, this.addEventPermission), 'deploy:deploy': this._loopEvents.bind(this, this.subscribeFunction), 'before:remove:remove': this._loopEvents.bind(this, this.unsubscribeFunction), 'subscribeExternalSNS:subscribe': this._loopEvents.bind(this, this.subscribeFunction), 'unsubscribeExternalSNS:unsubscribe': this._loopEvents.bind(this, this.unsubscribeFunction), }; this.commands = { subscribeExternalSNS: { usage: 'Adds subscriptions to any SNS Topics defined by externalSNS.', lifecycleEvents: [ 'subscribe' ], }, unsubscribeExternalSNS: { usage: 'Removes subscriptions from any SNS Topics defined by externalSNS.', lifecycleEvents: [ 'unsubscribe' ], }, }; }, _loopEvents: function(fn) { var self = this; _.each(this._serverless.service.functions, function(fnDef, fnName) { _.each(fnDef.events, function(evt) { if (evt.externalSNS) { fn.call(self, fnName, fnDef, evt.externalSNS); } }); }); }, addEventPermission: function(fnName, fnDef, topicName) { var normalizedFnName = this._normalize(fnName), normalizedTopicName = this._normalizeTopicName(topicName), fnRef = normalizedFnName + 'LambdaFunction', permRef = normalizedFnName + 'LambdaPermission' + normalizedTopicName, permission; permission = { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: { 'Fn::GetAtt': [ fnRef, 'Arn' ] }, Action: 'lambda:InvokeFunction', Principal: 'sns.amazonaws.com', SourceArn: { 'Fn::Join': [ ':', [ 'arn:aws:sns', { 'Ref': 'AWS::Region' }, { 'Ref': 'AWS::AccountId' }, topicName ] ] } }, }; this._serverless.service.provider.compiledCloudFormationTemplate.Resources[permRef] = permission; }, subscribeFunction: function(fnName, fnDef, topicName) { var self = this; if (this._opts.noDeploy) { this._serverless.cli.log( 'Not subscribing ' + fnDef.name + ' to ' + topicName + ' because of the noDeploy flag' ); return; } this._serverless.cli.log('Need to subscribe ' + fnDef.name + ' to ' + topicName); return this._getSubscriptionInfo(fnDef, topicName) .then(function(info) { var params; if (info.SubscriptionArn) { self._serverless.cli.log('Function ' + info.FunctionArn + ' is already subscribed to ' + info.TopicArn); return; } params = { TopicArn: info.TopicArn, Protocol: 'lambda', Endpoint: info.FunctionArn, }; return self.provider.request('SNS', 'subscribe', params, self._opts.stage, self._opts.region) .then(function() { self._serverless.cli.log('Function ' + info.FunctionArn + ' is now subscribed to ' + info.TopicArn); return; }); }); }, unsubscribeFunction: function(fnName, fnDef, topicName) { var self = this; this._serverless.cli.log('Need to unsubscribe ' + fnDef.name + ' from ' + topicName); return this._getSubscriptionInfo(fnDef, topicName) .then(function(info) { var params = { SubscriptionArn: info.SubscriptionArn }; if (!info.SubscriptionArn) { self._serverless.cli.log('Function ' + info.FunctionArn + ' is not subscribed to ' + info.TopicArn); return; } return self.provider.request('SNS', 'unsubscribe', params, self._opts.stage, self._opts.region) .then(function() { self._serverless.cli.log( 'Function ' + info.FunctionArn + ' is no longer subscribed to ' + info.TopicArn + ' (deleted ' + info.SubscriptionArn + ')' ); return; }); }); }, _getSubscriptionInfo: function(fnDef, topicName) { var self = this, fnArn, acctID, region, topicArn, params; params = { FunctionName: fnDef.name }; return this.provider.request('Lambda', 'getFunction', params, this._opts.stage, this._opts.region) .then(function(fn) { fnArn = fn.Configuration.FunctionArn; // NOTE: assumes that the topic is in the same account and region at this point region = fnArn.split(':')[3]; acctID = fnArn.split(':')[4]; topicArn = 'arn:aws:sns:' + region + ':' + acctID + ':' + topicName; self._serverless.cli.log('Function ARN: ' + fnArn); self._serverless.cli.log('Topic ARN: ' + topicArn); // NOTE: does not support NextToken and paginating through subscriptions at this point return self.provider.request('SNS', 'listSubscriptionsByTopic', { TopicArn: topicArn }, self._opts.stage, self._opts.region ); }) .then(function(resp) { var existing = _.findWhere(resp.Subscriptions, { Protocol: 'lambda', Endpoint: fnArn }) || {}; return { FunctionArn: fnArn, TopicArn: topicArn, SubscriptionArn: existing.SubscriptionArn, }; }); }, _normalize: function(s) { if (_.isEmpty(s)) { return; } return s[0].toUpperCase() + s.substr(1); }, _normalizeTopicName: function(arn) { return this._normalize(arn.replace(/[^0-9A-Za-z]/g, '')); }, });