serverless-offline-sqs-dlq
Version:
Enable Dead-Letter-Queue with AWS λ and SQS locally when developing your Serverless project
148 lines (131 loc) • 4.23 kB
JavaScript
const SQS = require('aws-sdk/clients/sqs')
const { get } = require('lodash/fp')
const extractQueueNameFromARN = (arn, service) => {
const getAtt = get(['Fn::GetAtt'], arn)
if (getAtt) {
const [resourceName] = getAtt
const properties = get(
['resources', 'Resources', resourceName, 'Properties'],
service,
)
if (properties && properties.QueueName) {
return properties.QueueName
}
return resourceName
} else {
const [, , , , , QueueName] = arn.split(':')
return QueueName
}
}
const GET_QUEUES_MAX_RETRIES = 5
const sleep = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms))
class ServerlessOfflineSQSDLQ {
constructor(serverless, options) {
this.serverless = serverless
this.service = serverless.service
this.options = options
this.hooks = {
'before:offline:start:init': this.offlineStartInit.bind(this),
'before:offline:start:end': this.offlineStartEnd.bind(this),
}
}
offlineStartInit() {
this.serverless.cli.log(`Starting Offline SQS DLQ.`)
const { functions, custom } = this.service
const configDLQ = custom['serverless-offline-sqs-dlq']
if (configDLQ) {
const client = new SQS({
...custom['serverless-offline-sqs'],
})
Object.keys(configDLQ).forEach(async (functionName) => {
const func = functions[functionName]
if (!func) {
this.serverless.cli.log(`λ not found: ${functionName}.`)
return
}
let QueueArn = func.events.map((e) => e.sqs).filter((f) => f)[0]
let QueueName
if (typeof QueueArn !== 'string' && QueueArn.arn !== undefined) {
QueueArn = QueueArn.arn
QueueName = QueueArn.QueueName
}
if (!QueueArn) {
this.serverless.cli.log(
`λ without SQS event source: ${functionName}.`,
)
return
}
if (!QueueName) {
QueueName = extractQueueNameFromARN(QueueArn, this.service)
}
const funcDLQ = functions[configDLQ[functionName].onError]
if (!funcDLQ) {
this.serverless.cli.log(
`λ DLQ not found: ${configDLQ[functionName]}.`,
)
return
}
let DeadLetterQueueArn = funcDLQ.events
.map((e) => e.sqs)
.filter((f) => f)[0]
if (
typeof DeadLetterQueueArn !== 'string' &&
DeadLetterQueueArn.arn !== undefined
) {
DeadLetterQueueArn = DeadLetterQueueArn.arn
}
if (DeadLetterQueueArn['Fn::GetAtt']) {
const dlqName = extractQueueNameFromARN(DeadLetterQueueArn, this.service)
DeadLetterQueueArn = `arn:aws:sqs:region:XXXXXX:${dlqName}`
}
if (!DeadLetterQueueArn) {
this.serverless.cli.log(
`λ without SQS event source: ${configDLQ[functionName].onError}.`,
)
return
}
this.serverless.cli.log(
`Creating DLQ of SQS event source, λ: ${functionName}.`,
)
let attempts = 0
let QueueUrl
while (!QueueUrl && attempts < GET_QUEUES_MAX_RETRIES) {
attempts += 1
try {
// eslint-disable-next-line no-await-in-loop
;({ QueueUrl } = await client
.getQueueUrl({
QueueName,
})
.promise())
} catch (e) {
if (attempts === GET_QUEUES_MAX_RETRIES) {
throw e
}
}
// eslint-disable-next-line no-await-in-loop
await sleep(1000)
}
try {
await client
.setQueueAttributes({
QueueUrl,
Attributes: {
RedrivePolicy: JSON.stringify({
deadLetterTargetArn: DeadLetterQueueArn,
maxReceiveCount: configDLQ[functionName].maxReceiveCount || 3,
}),
},
})
.promise()
} catch (e) {
this.serverless.cli.log(e.message)
}
})
}
}
offlineStartEnd() {
this.serverless.cli.log('offline-start-end')
}
}
module.exports = ServerlessOfflineSQSDLQ