@serverless-plugin-sqs-local/serverless-plugin-sqs-local
Version:
269 lines (263 loc) • 11.4 kB
JavaScript
'use strict';
var Sqs = require('aws-sdk/clients/sqs');
var mergeWith = require('lodash.mergewith');
var waitOn = require('wait-on');
var elasticmq = require('@serverless-plugin-sqs-local/elasticmq-localhost');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var Sqs__default = /*#__PURE__*/_interopDefaultLegacy(Sqs);
var mergeWith__default = /*#__PURE__*/_interopDefaultLegacy(mergeWith);
var waitOn__default = /*#__PURE__*/_interopDefaultLegacy(waitOn);
var elasticmq__default = /*#__PURE__*/_interopDefaultLegacy(elasticmq);
const Lambda = require('serverless-offline/dist/lambda').default;
const PLUGIN_NAME = 'serverless-plugin-sqs-local';
const commands = {
elasticmq: {
commands: {
install: {
usage: 'Installs ElasticMQ as local SQS',
lifecycleEvents: ['installHandler'],
options: {
ElasticMQDownloadUrl: {
type: 'string',
usage: 'Url to download ElasticMQ jar file. The default url is latest',
},
ElasticMQPath: {
type: 'string',
usage: 'Directory path to install ElasticMQ. The default directory is "$PWD/.elasticmq"',
},
},
},
start: {
lifecycleEvents: ['startHandler'],
usage: 'Starts local SQS',
options: {
ElasticMQPath: {
type: 'string',
usage: 'ElasticMQ installed directory path. The default directory is "$PWD/.elasticmq"',
},
ElasticMQPort: {
type: 'string',
usage: 'The port number that ElasticMQ will use to communicate with your application. The default port is 9324',
},
},
},
},
},
};
class ServerlessElasticMQ {
constructor(serverless, options) {
this.serverless = serverless;
this.installHandler = async () => {
const options = this.buildElasticMQOptions({
setup: {
downloadUrl: this.options.ElasticMQDownloadUrl,
installPath: this.options.ElasticMQPath,
},
});
await elasticmq__default["default"].install(options);
};
this.buildElasticMQOptions = (options) => {
const config = this.getConfig(PLUGIN_NAME);
return mergeWith__default["default"]({}, config.elasticmq, options, (a, b) => b === null || b === undefined ? a : undefined);
};
this.startHandler = async () => {
this.serverless.cli.log(`Starting Offline SQS...`);
try {
await this.startOfflineSQS();
}
catch (error) {
this.serverless.cli.log(`Failed to Start Offline SQS. ${error}`);
}
this.serverless.cli.log(`Creating Offline SQS Queues...`);
try {
await this.createOfflineSQSQueues();
}
catch (error) {
this.serverless.cli.log(`Failed to Create Offline SQS. ${error}`);
}
this.serverless.cli.log(`Starting Offline Kinesis...`);
return this.startOfflineKinesis();
};
this.startOfflineSQS = async () => {
const { ElasticMQPort, ElasticMQPath } = this.options;
const options = this.buildElasticMQOptions({
setup: {
installPath: ElasticMQPath,
},
start: {
port: ElasticMQPort,
},
});
this.port = elasticmq__default["default"].start(options);
await waitOn__default["default"]({
resources: [`${this.endpoint}?Action=ListQueues`],
});
};
this.createOfflineSQSQueues = async () => {
this.sqsClient = new Sqs__default["default"]({
endpoint: this.endpoint,
region: this.options.region || this.service.provider.region || 'us-east-1',
accessKeyId: 'root',
secretAccessKey: 'root',
});
if (this.service &&
this.service.resources &&
this.service.resources.Resources) {
const resources = this.service.resources.Resources;
const promises = Object.keys(resources)
.filter((key) => resources[key].Type === 'AWS::SQS::Queue')
.map((key) => this.createInitialQueue(key, resources[key].Properties));
await Promise.all(promises);
}
};
this.createInitialQueue = async (resourceName, queue) => {
this.serverless.cli.log(`Creating Queue ${resourceName}`);
const params = {
QueueName: queue.QueueName,
};
Object.keys(queue)
.filter((key) => key !== 'QueueName')
.forEach((key) => {
const attrs = {};
attrs[key] = queue[key].toString();
params.Attributes = attrs;
});
await this.sqsClient.createQueue(params).promise();
};
this.startOfflineKinesis = async () => {
const sqsEventHandlers = this.service
.getAllFunctions()
.reduce((eventHandlers, functionKey) => {
try {
const events = this.service.getEventInFunction('sqs', functionKey);
eventHandlers.push({
functionKey,
events,
});
}
catch {
// throw error when not exist
}
return eventHandlers;
}, []);
const lambdas = sqsEventHandlers.map(({ functionKey }) => {
const functionDefinition = this.service.getFunction(functionKey);
return {
functionKey,
functionDefinition,
};
});
this.lambda = new Lambda(this.serverless, {
location: this.getConfig(PLUGIN_NAME).location,
});
this.lambda.create(lambdas);
const promises = sqsEventHandlers.map(({ functionKey, events }) => {
return this.createQueueReadable(functionKey, events['sqs']);
});
return promises;
};
this.createQueueReadable = async (functionKey, queueEvent) => {
const queueName = this.getQueueName(queueEvent);
const { QueueUrl } = await this.sqsClient.getQueueUrl({
QueueName: queueName,
}).promise();
if (!QueueUrl) {
return;
}
const next = async () => {
const { Messages } = await this.sqsClient.receiveMessage({
QueueUrl,
MaxNumberOfMessages: queueEvent.batchSize,
WaitTimeSeconds: 1,
}).promise();
if (Messages) {
await this.handleEvent(queueEvent, functionKey, Messages);
await this.sqsClient.deleteMessageBatch({
Entries: Messages.map(({ MessageId: Id, ReceiptHandle }) => ({
Id: Id,
ReceiptHandle: ReceiptHandle,
})),
QueueUrl,
}).promise();
}
await next();
};
return next();
};
this.getQueueName = (queueEvent) => {
if (typeof queueEvent === 'string') {
return this.extractQueueNameFromARN(queueEvent);
}
if (typeof queueEvent.arn === 'string') {
return this.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`);
};
this.extractQueueNameFromARN = (arn) => {
const [, , , , , QueueName] = arn.split(':');
return QueueName;
};
this.handleEvent = async (queueEvent, functionName, messages) => {
const streamName = this.getQueueName(queueEvent);
this.serverless.cli.log(`${streamName} (λ: ${functionName})`);
const lambdaFunction = this.lambda.get(functionName);
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: this.options.region || this.service.provider.region,
})),
};
lambdaFunction.setEvent(event);
await lambdaFunction.runHandler();
};
this.stopHandler = async () => {
this.serverless.cli.log('Stopping Offline SQS...');
if (this.port) {
elasticmq__default["default"].stop(this.port);
}
};
this.options = options;
this.service = serverless.service;
this.commands = commands;
this.hooks = {
'elasticmq:install:installHandler': this.installHandler,
'elasticmq:start:startHandler': this.startHandler,
'before:offline:start:init': this.startHandler,
'before:offline:start:end': this.stopHandler,
};
}
getConfig(name) {
return ((this.service &&
this.service.custom &&
this.service.custom[name]) ||
{});
}
get endpoint() {
return `http://localhost:${this.port}`;
}
}
module.exports = ServerlessElasticMQ;
//# sourceMappingURL=index.js.map