UNPKG

serverless-offline-sqs-esmq

Version:
253 lines (199 loc) 6.79 kB
const {join} = require('path'); const figures = require('figures'); const SQS = require('aws-sdk/clients/sqs'); const {mapValues, isEmpty, forEach, map, has, filter, get, pipe} = require('lodash/fp'); const {createHandler, getFunctionOptions} = require('serverless-offline/src/functionHelper'); const createLambdaContext = require('serverless-offline/src/createLambdaContext'); const fromCallback = fun => new Promise((resolve, reject) => { fun((err, data) => { if (err) return reject(err); resolve(data); }); }); const printBlankLine = () => console.log(); const getConfig = (service, pluginName) => { return (service && service.custom && service.custom[pluginName]) || {}; }; const extractQueueNameFromARN = arn => { const [, , , , , QueueName] = arn.split(':'); return QueueName; }; class ServerlessOfflineSQS { constructor(serverless, options) { this.serverless = serverless; this.service = serverless.service; this.options = options; this.config = getConfig(this.service, 'serverless-offline-sqs-esmq'); this.commands = {}; this.hooks = { 'before:offline:start:init': this.offlineStartInit.bind(this), 'before:offline:start:end': this.offlineStartEnd.bind(this) }; this.streams = []; } getClient() { const awsConfig = Object.assign( { region: this.options.region || this.service.provider.region || 'us-west-2' }, this.config ); return new SQS(awsConfig); } getQueueName(queueEvent) { if (typeof queueEvent === 'string') return extractQueueNameFromARN(queueEvent); if (typeof queueEvent.arn === 'string') return extractQueueNameFromARN(queueEvent.arn); if (typeof queueEvent.queueName === 'string') return queueEvent.queueName; if (queueEvent.arn['Fn::GetAtt']) { const [ResourceName] = queueEvent.arn['Fn::GetAtt']; if ( this.service && this.service.resources && this.service.resources.Resources && this.service.resources.Resources[ResourceName] && this.service.resources.Resources[ResourceName].Properties && typeof this.service.resources.Resources[ResourceName].Properties.QueueName === 'string' ) return this.service.resources.Resources[ResourceName].Properties.QueueName; } throw new Error( `QueueName not found` ); } eventHandler(queueEvent, functionName, messages, cb) { if (!messages) return cb(); const streamName = this.getQueueName(queueEvent); this.serverless.cli.log(`${streamName} (λ: ${functionName})`); const {location = '.'} = getConfig(this.service, 'serverless-offline'); const __function = this.service.getFunction(functionName); const servicePath = join(this.serverless.config.servicePath, location); const funOptions = getFunctionOptions(__function, functionName, servicePath); const handler = createHandler(funOptions, {}); const lambdaContext = createLambdaContext(__function, (err, data) => { this.serverless.cli.log( `[${err ? figures.cross : figures.tick}] ${JSON.stringify(data) || ''}` ); cb(err, data); }); const event = { Records: messages.map( ({ MessageId: messageId, ReceiptHandle: receiptHandle, Body: body, Attributes: attributes, MessageAttributes: messageAttributes, MD5OfBody: md5OfBody }) => ({ messageId, receiptHandle, body, attributes, messageAttributes, md5OfBody, eventSource: 'aws:sqs', eventSourceARN: queueEvent.arn, awsRegion: 'us-west-2' }) ) }; if (handler.length < 3) handler(event, lambdaContext) .then(res => lambdaContext.done(null, res)) .catch(lambdaContext.done); else handler(event, lambdaContext, lambdaContext.done); } async createInitialQueue(queue) { const client = await this.getClient(); const params = { QueueName: queue.QueueName, /* required */ Attributes: {} }; forEach(attribute => { if (attribute !== 'QueueName') { params.Attributes[attribute] = queue[attribute].toString(); } }, Object.keys(queue)); client.createQueue(params, (err) => { if (err) console.log(err); }); } async createQueueReadable(functionName, queueEvent) { const client = this.getClient(); const queueName = this.getQueueName(queueEvent); this.serverless.cli.log(`${queueName}`); const {QueueUrl} = await fromCallback(cb => client.getQueueUrl( { QueueName: queueName }, cb ) ); const next = async () => { const {Messages} = await fromCallback(cb => client.receiveMessage( { QueueUrl, MaxNumberOfMessages: queueEvent.batchSize, WaitTimeSeconds: 1 }, cb ) ); if (Messages) { await fromCallback(cb => this.eventHandler(queueEvent, functionName, Messages, cb)); await fromCallback(cb => client.deleteMessageBatch( { Entries: (Messages || []).map(({MessageId: Id, ReceiptHandle}) => ({ Id, ReceiptHandle })), QueueUrl }, () => cb() ) ); } next(); }; next(); } offlineStartInit() { this.serverless.cli.log(`Creating Offline SQS Queues.`); if ( this.service && this.service.resources && this.service.resources.Resources ) { const resouces = Object.keys(this.service.resources.Resources); forEach(resourceName => { if (this.service.resources.Resources[resourceName].Type === 'AWS::SQS::Queue') { this.serverless.cli.log(`Creating Queue ${resourceName}`); this.createInitialQueue(this.service.resources.Resources[resourceName].Properties) } }, resouces); printBlankLine(); } this.serverless.cli.log(`Starting Offline Kinesis.`); mapValues.convert({cap: false})((_function, functionName) => { const queues = pipe(get('events'), filter(has('sqs')), map(get('sqs')))(_function); if (!isEmpty(queues)) { printBlankLine(); this.serverless.cli.log(`SQS for ${functionName}:`); } forEach(queueEvent => { this.createQueueReadable(functionName, queueEvent); }, queues); if (!isEmpty(queues)) { printBlankLine(); } }, this.service.functions); } offlineStartEnd() { this.serverless.cli.log('offline-start-end'); } } module.exports = ServerlessOfflineSQS;