UNPKG

serverless-offline-sqs-external

Version:

Emulate AWS Lambda ans SQS locally by listening to external SQS

277 lines (237 loc) 9.14 kB
"use strict"; var _awsSdk = require("aws-sdk"); var _url = require("url"); var _helpers = require("./helpers"); class ServerlessOfflineSQSExternal { constructor(serverless, options) { this.serverless = serverless; this.service = serverless.service; this.options = options; this.commands = {}; this.hooks = { 'before:offline:start': this.offlineStartInit.bind(this), 'before:offline:start:init': this.offlineStartInit.bind(this), 'before:offline:start:end': this.offlineStartEnd.bind(this) }; this.streams = []; } getOfflineConfig() { var _this$service$custom; const config = ((_this$service$custom = this.service.custom) === null || _this$service$custom === void 0 ? void 0 : _this$service$custom['serverless-offline']) || {}; return { ...config, endpoint: config.endpoint || 'http://localhost:3002' }; } getSqsConfig() { var _this$service$custom2; const config = ((_this$service$custom2 = this.service.custom) === null || _this$service$custom2 === void 0 ? void 0 : _this$service$custom2['serverless-offline-sqs-external']) || {}; let { endpoint, host = 'localhost', port = 4576, https = false } = config; if (!endpoint) { endpoint = `${https ? 'https://' : 'http://'}${host}:${port}`; } else { try { const url = new _url.URL(endpoint); host = url.hostname; port = url.port; https = url.protocol; } catch (err) { host = endpoint; } } return { ...config, host, port, endpoint }; } getConfig() { return { ...this.options, ...this.service, ...this.service.provider }; } getClient() { const config = this.getSqsConfig(); const sqsConfig = { endpoint: new _awsSdk.Endpoint(config.endpoint), region: config.region, credentials: new _awsSdk.Credentials({ accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey }) }; return new _awsSdk.SQS(sqsConfig); } getProperties(queueEvent) { var _queueEvent$arn; const getAtt = queueEvent === null || queueEvent === void 0 ? void 0 : (_queueEvent$arn = queueEvent.arn) === null || _queueEvent$arn === void 0 ? void 0 : _queueEvent$arn['Fn::GetAtt']; if (getAtt) { var _this$service, _this$service$resourc, _this$service$resourc2, _this$service$resourc3; const [resourceName] = getAtt; const properties = (_this$service = this.service) === null || _this$service === void 0 ? void 0 : (_this$service$resourc = _this$service.resources) === null || _this$service$resourc === void 0 ? void 0 : (_this$service$resourc2 = _this$service$resourc.Resources) === null || _this$service$resourc2 === void 0 ? void 0 : (_this$service$resourc3 = _this$service$resourc2[resourceName]) === null || _this$service$resourc3 === void 0 ? void 0 : _this$service$resourc3.Properties; if (!properties) throw new Error(`No resource defined with name ${resourceName}`); return Object.entries(properties).map(([key, value]) => { if (!(0, _helpers.isPlainObject)(value)) return [key, value.toString()]; if (Object.keys(value).some(k => k === 'Ref' || k.startsWith('Fn::')) || Object.values(value).some(_helpers.isPlainObject)) { return this.serverless.cli.log(`WARN ignore property '${key}' in config as it is some cloudformation reference: ${JSON.stringify(value)}`); } return [key, JSON.stringify(value)]; }).filter(val => !(0, _helpers.isFalsey)(val)).reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {}); } return null; } getProperty(queueEvent, propertyName) { const properties = this.getProperties(queueEvent); return (properties === null || properties === void 0 ? void 0 : properties[propertyName]) || null; } getQueueName(queueEvent) { if (typeof queueEvent === 'string') return (0, _helpers.extractQueueNameFromARN)(queueEvent); if (typeof queueEvent.arn === 'string') return (0, _helpers.extractQueueNameFromARN)(queueEvent.arn); if (typeof queueEvent.queueName === 'string') return queueEvent.queueName; const queueName = this.getProperty(queueEvent, 'QueueName'); if (!queueName) { throw new Error('QueueName not found. See https://github.com/CoorpAcademy/serverless-plugins/tree/master/packages/serverless-offline-sqs#functions'); } return queueName; } async eventHandler(queueEvent, functionName, messages) { var _this$service2, _this$service2$provid; if (!messages) return Promise.resolve(); const streamName = this.getQueueName(queueEvent); this.serverless.cli.log(`${streamName} (λ: ${functionName})`); const config = this.getConfig(); const awsRegion = config.region || 'us-east-1'; const awsAccountId = config.accountId || '000000000000'; const eventSourceARN = typeof queueEvent.arn === 'string' ? queueEvent.arn : `arn:aws:sqs:${awsRegion}:${awsAccountId}:${streamName}`; const func = this.service.getFunction(functionName); const { env } = process; const functionEnv = { ...{ AWS_REGION: awsRegion }, ...(env || {}), ...((this === null || this === void 0 ? void 0 : (_this$service2 = this.service) === null || _this$service2 === void 0 ? void 0 : (_this$service2$provid = _this$service2.provider) === null || _this$service2$provid === void 0 ? void 0 : _this$service2$provid.environment) || {}), ...((func === null || func === void 0 ? void 0 : func.environment) || {}) }; process.env = functionEnv; 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, awsRegion })) }; const offlineConfg = this.getOfflineConfig(); const lambdaConfig = { apiVersion: '2015-03-31', endpoint: new _awsSdk.Endpoint(offlineConfg.endpoint), region: awsRegion, credentials: new _awsSdk.Credentials({ accessKeyId: 'foo', secretAccessKey: 'bar' }) }; const lambda = new _awsSdk.Lambda(lambdaConfig); const params = { FunctionName: `${this.service.service}-${this.service.provider.stage}-${functionName}`, InvocationType: 'Event', Payload: JSON.stringify(event) }; await lambda.invoke(params).promise(); process.env = env; return null; } async createQueueReadable(functionName, queueEvent) { const client = this.getClient(); const QueueName = this.getQueueName(queueEvent); this.serverless.cli.log(`Queue Name: ${QueueName}`); const sqsConfig = this.getSqsConfig(); if (sqsConfig.autoCreate) { const properties = this.getProperties(queueEvent); const params = { QueueName, Attributes: (0, _helpers.omit)(['QueueName'], properties) }; await client.createQueue(params).promise(); } let { QueueUrl } = await client.getQueueUrl({ QueueName }).promise(); QueueUrl = QueueUrl.replace('localhost', sqsConfig.host); const next = async () => { const { Messages } = await client.receiveMessage({ QueueUrl, MaxNumberOfMessages: queueEvent.batchSize, AttributeNames: ['All'], MessageAttributeNames: ['All'], WaitTimeSeconds: 20 }).promise(); if (Messages) { try { await this.eventHandler(queueEvent, functionName, Messages); await client.deleteMessageBatch({ Entries: (Messages || []).map(({ MessageId: Id, ReceiptHandle }) => ({ Id, ReceiptHandle })), QueueUrl }).promise(); } catch (err) { this.serverless.cli.log(err.stack); } } next(); }; next(); } offlineStartInit() { this.serverless.cli.log('Starting Offline SQS.'); Object.entries(this.service.functions).map(([functionName, _function]) => { const queues = ((_function === null || _function === void 0 ? void 0 : _function.events) || []).filter(event => event === null || event === void 0 ? void 0 : event.sqs).map(event => event.sqs); if (!(0, _helpers.isEmpty)(queues)) { (0, _helpers.printBlankLine)(); this.serverless.cli.log(`SQS for ${functionName}:`); } queues.forEach(queueEvent => { this.createQueueReadable(functionName, queueEvent); }); if (!(0, _helpers.isEmpty)(queues)) { (0, _helpers.printBlankLine)(); } return null; }); } offlineStartEnd() { this.serverless.cli.log('offline-start-end'); } } module.exports = ServerlessOfflineSQSExternal; //# sourceMappingURL=index.js.map