cloud-red
Version:
Harnessing Serverless for your cloud integration needs
241 lines (216 loc) • 6.83 kB
JavaScript
module.exports = function(RED) {
'use strict';
const claudia = require('claudia');
const AWS = require('aws-sdk');
const path = require('path');
var awsLib = require('./lib/aws');
class S3EventHandler {
/**
* @typedef {Object} Rule
* @property {string} t - AWS Event's name
* @property {string} v - AWS Event's label
* @property {string} kind - Legacy. only use value: `V`
*/
/**
* AWS S3 Event Handler Node
* @param {Object} props - LambdaHandler Properties
* @param {string} props.name - URLs to set up the routes
* @param {string} props.method - Method to set up the routes (for AWSHandler, only method used will be `post`)
* @param {Rule[]} props.rules - Mapping of the AWS Events to the outgoing ports.export
*
*/
constructor(props) {
RED.nodes.createNode(this, props);
// public props
this.name = props.name;
this.triggerEnabled = props.triggerEnabled;
this.bucketName = props.bucketName;
this.prefix = props.prefix;
this.suffix = props.suffix;
this.event = props.event;
// handlers & middleware
this.close = this.close.bind(this);
this._createResponseWrapper = this._createResponseWrapper.bind(this);
this.callback = this.callback.bind(this);
this.getBucketNotifications = this.getBucketNotifications.bind(this);
this.checkIfEventsExist = this.checkIfEventsExist.bind(this);
this.postEditorDeployment = this.postEditorDeployment.bind(this);
// Init handler
this.log(`Registering S3 handler ...`);
RED.httpNode.mountEventHandler(this.callback);
RED.events.on('editor:post-deployment', this.postEditorDeployment);
}
// Unregister our handlers when the flow is closed
close() {
this.log('Unregistering s3 event handler ...');
RED.httpNode.unmountEventHandler(this.callback);
this.log('Removing editor lifecycle hooks ...');
RED.events.removeListener(
'editor:post-deployment',
this.postEditorDeployment
);
}
_createResponseWrapper(res) {
var wrapper = {
_res: res
};
var toWrap = [
'append',
'attachment',
'cookie',
'clearCookie',
'download',
'end',
'format',
'get',
'json',
'jsonp',
'links',
'location',
'redirect',
'render',
'send',
'sendFile',
'sendStatus',
'set',
'status',
'type',
'vary'
];
toWrap.forEach(f => {
wrapper[f] = function() {
// node here before - pretty sure the this points ot the this of the outer function
this.warn(
RED._('httpin.errors.deprecated-call', { method: 'msg.res.' + f })
);
var result = res[f].apply(res, arguments);
if (result === res) {
return wrapper;
} else {
return result;
}
};
});
return wrapper;
}
callback(req, res, next) {
// Identify what type of AWS event encapsulates the incoming request
let awsEventType = awsLib.readTypeEvent(req);
if (awsEventType === awsLib.AWSEventTypes.S3Event) {
this.send({
_msgid: msgid,
_awsEventType: awsEventType,
req: req,
res: this._createResponseWrapper(res),
payload: req.body
});
} else {
// pass the request to the next middleware
return next();
}
}
// Trigger
async getBucketNotifications(s3EventOpts) {
// Make sure the following event is not registered in the bucket
let events = this.events
? this.events.split(',')
: ['s3:ObjectCreated:*'];
AWS.config.credentials = new AWS.SharedIniFileCredentials({
profile: 'personal'
});
try {
const s3 = new AWS.S3();
const config = await s3
.getBucketNotificationConfiguration({
Bucket: s3EventOpts.bucket
})
.promise();
const exist = this.checkIfEventsExist(
config.LambdaFunctionConfigurations,
events
);
if (exist) {
this.log('Removing existing S3 Event Notification ...');
const result = await s3
.putBucketNotificationConfiguration({
Bucket: s3EventOpts.bucket,
NotificationConfiguration: {}
})
.promise();
return result;
}
} catch (err) {
this.error(
`Error while trying to read existing notifications for bucket: ${this.bucketName}`
);
throw err;
}
}
checkIfEventsExist(lambdaConfigs, eventsToMatch) {
if (!lambdaConfigs && lambdaConfigs.length === 0) {
return false;
}
let BreakException = {}; // hack as there is no built-in ability to break in forEach. Js, c'mon!!
let found = false;
try {
lambdaConfigs.forEach(configs => {
if (!configs.Events && configs.Events.length === 0) {
found = false;
} else {
configs.Events.forEach(event => {
if (eventsToMatch.indexOf(event) >= 0) {
// found it
found = true;
throw BreakException;
}
});
}
});
} catch (error) {
if (error !== BreakException) throw e;
return found;
}
return found;
}
async postEditorDeployment() {
//RED.settings.getUserSettings();
this.log('Attaching s3 event to lambda...');
const userDir = RED.settings.userDir;
let s3EventOpts = {
bucket: this.bucketName, // mandatory
source: userDir,
config: path.join(userDir, 'cloud-red-config.json')
};
if (this.prefix) {
s3EventOpts.prefix = this.prefix;
}
if (this.suffix) {
s3EventOpts.suffix = this.suffix;
}
if (this.events) {
s3EventOpts.events = this.events;
}
// Clean up bucket notification if exists
try {
await this.getBucketNotifications(s3EventOpts);
} catch (err) {
throw err;
}
AWS.config.credentials = new AWS.SharedIniFileCredentials({
profile: 'personal'
});
claudia
.addS3EventSource(s3EventOpts)
.then(() => {
this.log(
`Lambda configured to receive S3 Event notification from bucket: ${this.bucketName}`
);
})
.catch(err => {
this.error('Error while attaching an S3 event to lambda');
console.log(err);
});
}
}
RED.nodes.registerType('s3-event-handler', S3EventHandler);
};