mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
316 lines
11 KiB
JavaScript
316 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Action: Event Remove
|
|
* - Removes Event Sources from a lambda function
|
|
* - Loops sequentially through each Region in specified Stage
|
|
*
|
|
* Options:
|
|
* - stage: (String) The stage to remove from
|
|
* - region: (String) The region in the stage to remove from
|
|
* - paths: (Array) Array of event paths to remove. Format: ['users/show#eventName']
|
|
* - all: (Boolean) Indicates whether all Events in the project should be removed.
|
|
*/
|
|
|
|
module.exports = function(SPlugin, serverlessPath) {
|
|
|
|
const path = require('path'),
|
|
SError = require(path.join(serverlessPath, 'Error')),
|
|
SCli = require(path.join(serverlessPath, 'utils/cli')),
|
|
BbPromise = require('bluebird'),
|
|
_ = require('lodash');
|
|
|
|
let SUtils;
|
|
|
|
class EventRemove extends SPlugin {
|
|
|
|
constructor(S) {
|
|
super(S);
|
|
SUtils = S.utils;
|
|
}
|
|
|
|
static getName() {
|
|
return 'serverless.core.' + this.name;
|
|
}
|
|
|
|
registerActions() {
|
|
|
|
this.S.addAction(this.eventRemove.bind(this), {
|
|
handler: 'eventRemove',
|
|
description: 'Removes event sources from lambdas',
|
|
context: 'event',
|
|
contextAction: 'remove',
|
|
options: [
|
|
{
|
|
option: 'stage',
|
|
shortcut: 's',
|
|
description: 'Optional if only one stage is defined in project'
|
|
}, {
|
|
option: 'region',
|
|
shortcut: 'r',
|
|
description: 'Optional - Target one region to remove from'
|
|
}, {
|
|
option: 'all',
|
|
shortcut: 'a',
|
|
description: 'Optional - Remove all Events'
|
|
}
|
|
],
|
|
parameters: [
|
|
{
|
|
parameter: 'names',
|
|
description: 'One or multiple event names',
|
|
position: '0->'
|
|
}
|
|
]
|
|
});
|
|
|
|
return BbPromise.resolve();
|
|
}
|
|
|
|
eventRemove(evt) {
|
|
return EventRemover.run(this.S, evt);
|
|
}
|
|
}
|
|
|
|
|
|
class EventRemover extends SPlugin {
|
|
|
|
constructor(S, evt) {
|
|
super(S);
|
|
this.evt = evt;
|
|
|
|
// Instantiate Classes
|
|
this.project = this.S.getProject();
|
|
this.aws = this.S.getProvider('aws');
|
|
}
|
|
|
|
static run(evt, S) {
|
|
let remover = new this(evt, S);
|
|
return remover.eventRemove();
|
|
}
|
|
|
|
eventRemove() {
|
|
return BbPromise.try(() => {
|
|
// Prompt: Stage
|
|
if (!this.S.config.interactive || this.evt.options.stage) return;
|
|
|
|
return this.cliPromptSelectStage('Event Remover - Choose a stage: ', this.evt.options.stage, false)
|
|
.then(stage => this.evt.options.stage = stage)
|
|
})
|
|
.bind(this)
|
|
.then(this._validateAndPrepare)
|
|
.then(this._processRemoval)
|
|
.then(function() {
|
|
this._displaySuccessful();
|
|
this._displayFailed();
|
|
|
|
/**
|
|
* Return EVT
|
|
*/
|
|
|
|
this.evt.data.removed = this.removed;
|
|
this.evt.data.failed = this.failed;
|
|
return this.evt;
|
|
});
|
|
}
|
|
|
|
_displayFailed() {
|
|
if(!this.failed) return;
|
|
|
|
SCli.log(`Failed to remove events in "${this.evt.options.stage}" from the following regions:`);
|
|
|
|
_.each(this.failed, (failed, region) => {
|
|
SCli.log(region + ' ------------------------');
|
|
_.each(failed, (fail) => SCli.log(` ${fail.name}: ${fail.message}`));
|
|
});
|
|
|
|
SCli.log('');
|
|
SCli.log('Run this again with --debug to get more error information...');
|
|
}
|
|
|
|
_displaySuccessful() {
|
|
if (!this.removed) return;
|
|
|
|
SCli.log(`Successfully removed events in "${this.evt.options.stage}" from the following regions:`);
|
|
|
|
_.each(this.removed, (removed, region) => {
|
|
SCli.log(region + ' ------------------------');
|
|
_.each(removed, (event) => SCli.log(` ${event.name}`));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate And Prepare
|
|
* - If CLI, maps CLI input to event object
|
|
*/
|
|
|
|
_validateAndPrepare() {
|
|
let _this = this;
|
|
// Set defaults
|
|
this.evt.options.names = this.evt.options.names || [];
|
|
this.regions = this.evt.options.region ? [this.evt.options.region] : this.project.getAllRegionNames(this.evt.options.stage);
|
|
|
|
this.events = _.map(this.evt.options.names, (name) => this.project.getEvent(name));
|
|
|
|
// If CLI and no event names targeted, remove by CWD
|
|
if (this.S.cli && !this.evt.options.names.length && !this.evt.options.all) {
|
|
let functionsByCwd = SUtils.getFunctionsByCwd(_this.project.getAllFunctions());
|
|
|
|
functionsByCwd.forEach(function(func) {
|
|
func.getAllEvents().forEach(function(event) {
|
|
_this.events.push(event);
|
|
});
|
|
});
|
|
}
|
|
|
|
// If --all is selected, load all paths
|
|
if (this.evt.options.all) {
|
|
this.events = this.project.getAllEvents();
|
|
}
|
|
|
|
// Validate Stage
|
|
if (!this.evt.options.stage || !this.project.validateStageExists(this.evt.options.stage)) {
|
|
throw new SError(`Stage is required`);
|
|
}
|
|
|
|
return BbPromise.resolve();
|
|
}
|
|
|
|
_processRemoval() {
|
|
|
|
// Status
|
|
SCli.log(`\nRemoving events in "${this.evt.options.stage}" to the following regions: ${this.regions.join(', ')}`);
|
|
|
|
let spinner = SCli.spinner();
|
|
spinner.start();
|
|
|
|
return BbPromise
|
|
.map(this.regions, this._removeByRegion.bind(this))
|
|
.then(() => spinner.stop(true)); // Stop Spinner
|
|
}
|
|
|
|
_removeByRegion(region) {
|
|
return BbPromise.map(this.events, ((event) => this._eventRemove(event, region)), {concurrency: 5});
|
|
}
|
|
|
|
_eventRemove(event, region) {
|
|
if(!event) throw new SError(`Event could not be found: ${event.name}`);
|
|
|
|
let eventType = event.type.toLowerCase();
|
|
|
|
|
|
if (['dynamodbstream', 'kinesisstream'].indexOf(eventType) > -1) eventType = 'LambdaStream';
|
|
|
|
if (!this[`_remove_${eventType}`]) {
|
|
SCli.log(`WARNING: Event type "${event.type}" removal is not supported yet`);
|
|
return BbPromise.resolve();
|
|
}
|
|
|
|
return this[`_remove_${eventType}`](event, region)
|
|
.then((result) => {
|
|
// Stash removed events
|
|
if (!this.removed) this.removed = {};
|
|
if (!this.removed[region]) this.removed[region] = [];
|
|
this.removed[region].push({
|
|
function: event.getFunction(),
|
|
name: event.name
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
// Stash Failed Events
|
|
if (!this.failed) this.failed = {};
|
|
if (!this.failed[region]) this.failed[region] = [];
|
|
this.failed[region].push({
|
|
function: event.getFunction() || 'unknown',
|
|
name: event.name,
|
|
message: e.message,
|
|
stack: e.stack
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
_remove_LambdaStream(event, region) {
|
|
const stage = this.evt.options.stage,
|
|
functionName = event.getFunction().getDeployedName({stage, region}),
|
|
regionVars = this.project.getRegion(stage, region).getVariables(),
|
|
awsAccountId = regionVars.iamRoleArnLambda.split('::')[1].split(':')[0],
|
|
arn = 'arn:aws:lambda:' + region + ':' + awsAccountId + ':function:' + functionName + ':' + stage,
|
|
eventId = 'eventID:' + event.name,
|
|
UUID = regionVars[eventId];
|
|
|
|
if (!UUID) return BbPromise.reject(new SError(`EventSourceMapping UUID for "${event.name}" is not found`));
|
|
|
|
return this.aws.request('Lambda', 'deleteEventSourceMapping', {UUID}, stage, region).then(() => {
|
|
let regionInstance = this.project.getRegion(stage, region)
|
|
delete regionInstance.getVariables()[eventId];
|
|
return regionInstance.save();
|
|
});
|
|
|
|
}
|
|
|
|
_remove_s3(event, region) {
|
|
const stage = this.evt.options.stage,
|
|
populatedEvent = event.toObjectPopulated({stage, region}),
|
|
Bucket = populatedEvent.config.bucket,
|
|
regionVars = this.project.getRegion(stage, region).getVariables(),
|
|
functionName = event.getFunction().getDeployedName({stage, region}),
|
|
awsAccountId = regionVars.iamRoleArnLambda.split('::')[1].split(':')[0],
|
|
arn = 'arn:aws:lambda:' + region + ':' + awsAccountId + ':function:' + functionName + ':' + stage,
|
|
s3Region = this.project.getVariables().projectBucket.split('.')[1];
|
|
|
|
return this.aws.request('S3', 'getBucketNotificationConfiguration', {Bucket}, stage, s3Region)
|
|
.then((conf) => {
|
|
if (!_.find(conf.LambdaFunctionConfigurations, {LambdaFunctionArn: arn})) {
|
|
return BbPromise.reject(new SError(`S3 configuration for "${event.name}" is not found`))
|
|
}
|
|
conf.LambdaFunctionConfigurations = _.filter(conf.LambdaFunctionConfigurations, (item) => item.LambdaFunctionArn !== arn );
|
|
return this.aws.request('S3', 'putBucketNotificationConfiguration', {Bucket, NotificationConfiguration: conf}, stage, s3Region);
|
|
});
|
|
}
|
|
|
|
_remove_schedule(event, region) {
|
|
const stage = this.evt.options.stage,
|
|
functionName = event.getFunction().getDeployedName({stage, region}),
|
|
Rule = functionName + '-' + event.name;
|
|
|
|
return this.aws.request('CloudWatchEvents', 'removeTargets', {Ids: [functionName], Rule}, stage, region)
|
|
.then(() => this.aws.request('CloudWatchEvents', 'deleteRule', {Name: Rule}, stage, region));
|
|
}
|
|
|
|
_remove_sns(event, region) {
|
|
const stage = this.evt.options.stage,
|
|
functionName = event.getFunction().getDeployedName({stage, region}),
|
|
regionVars = this.project.getRegion(stage, region).getVariables(),
|
|
awsAccountId = regionVars.iamRoleArnLambda.split('::')[1].split(':')[0],
|
|
Endpoint = 'arn:aws:lambda:' + region + ':' + awsAccountId + ':function:' + functionName + ':' + stage,
|
|
populatedEvent = event.toObjectPopulated({stage, region}),
|
|
TopicArn = 'arn:aws:sns:' + region + ':' + awsAccountId + ':' + populatedEvent.config.topicName;
|
|
|
|
return this._SNSlistSubscriptionsByTopic(TopicArn, stage, region)
|
|
.then((subscriptions) => _.filter(subscriptions, {Endpoint}))
|
|
.then((subscriptions) => subscriptions.length && subscriptions || BbPromise.reject(new SError(`Subscription for "${event.name}" is not found`)))
|
|
.map((subscription) => subscription.SubscriptionArn)
|
|
.map((SubscriptionArn) => this.aws.request('SNS', 'unsubscribe', {SubscriptionArn}, stage, region));
|
|
}
|
|
|
|
_SNSlistSubscriptionsByTopic(TopicArn, stage, region, NextToken, subscriptions) {
|
|
subscriptions = subscriptions || [];
|
|
|
|
return this.aws.request('SNS', 'listSubscriptionsByTopic', {TopicArn, NextToken}, stage, region)
|
|
.then((reply) => {
|
|
subscriptions = subscriptions.concat(reply.Subscriptions);
|
|
if (reply.NextToken) {
|
|
return this._SNSlistSubscriptionsByTopic(TopicArn, stage, region, reply.NextToken, subscriptions);
|
|
} else {
|
|
return subscriptions;
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return EventRemove;
|
|
};
|