UNPKG

cloud-red

Version:

Harnessing Serverless for your cloud integration needs

241 lines (216 loc) 6.83 kB
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); };