mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
302 lines
11 KiB
JavaScript
302 lines
11 KiB
JavaScript
import _ from 'lodash'
|
|
import ServerlessError from '../../../../../serverless-error.js'
|
|
import resolveLambdaTarget from '../../../utils/resolve-lambda-target.js'
|
|
|
|
class AwsCompileSNSEvents {
|
|
constructor(serverless, options) {
|
|
this.serverless = serverless
|
|
this.provider = this.serverless.getProvider('aws')
|
|
this.options = options
|
|
this.hooks = {
|
|
'package:compileEvents': async () => this.compileSNSEvents(),
|
|
}
|
|
|
|
this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'sns', {
|
|
anyOf: [
|
|
{ type: 'string', maxLength: 256, pattern: '^[\\w-]+$' },
|
|
{ $ref: '#/definitions/awsArnString' },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
arn: { $ref: '#/definitions/awsArn' },
|
|
topicName: { type: 'string', maxLength: 256, pattern: '^[\\w-]+$' },
|
|
displayName: { type: 'string', minLength: 1 },
|
|
filterPolicy: { type: 'object' },
|
|
filterPolicyScope: { type: 'string' },
|
|
redrivePolicy: {
|
|
type: 'object',
|
|
properties: {
|
|
deadLetterTargetArn: { $ref: '#/definitions/awsArnString' },
|
|
deadLetterTargetRef: { type: 'string', minLength: 1 },
|
|
deadLetterTargetImport: {
|
|
type: 'object',
|
|
properties: {
|
|
arn: { $ref: '#/definitions/awsArnString' },
|
|
url: { type: 'string', minLength: 1 },
|
|
},
|
|
required: ['arn', 'url'],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
oneOf: [
|
|
{ required: ['deadLetterTargetArn'] },
|
|
{ required: ['deadLetterTargetRef'] },
|
|
{ required: ['deadLetterTargetImport'] },
|
|
],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
anyOf: [{ required: ['arn'] }, { required: ['topicName'] }],
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
compileSNSEvents() {
|
|
const template =
|
|
this.serverless.service.provider.compiledCloudFormationTemplate
|
|
|
|
this.serverless.service.getAllFunctions().forEach((functionName) => {
|
|
const functionObj = this.serverless.service.getFunction(functionName)
|
|
|
|
if (functionObj.events) {
|
|
functionObj.events.forEach((event) => {
|
|
if (event.sns) {
|
|
let topicArn
|
|
let topicName
|
|
let region
|
|
let redrivePolicy
|
|
if (typeof event.sns === 'object') {
|
|
if (event.sns.arn) {
|
|
topicArn = event.sns.arn
|
|
if (typeof topicArn === 'string') {
|
|
// NOTE: we need to process the topic ARN that way due to lacking
|
|
// support of regex lookbehind in Node.js 6.
|
|
// Once Node.js 6 support is dropped we can change this to:
|
|
// `const splitArn = topicArn.split(/(?<!:):(?!:)/);`
|
|
const splitArn = topicArn
|
|
.replace(/::/g, '@@')
|
|
.split(':')
|
|
.map((s) => s.replace(/@@/g, '::'))
|
|
topicName = splitArn[splitArn.length - 1]
|
|
if (splitArn[3] !== this.options.region) {
|
|
region = splitArn[3]
|
|
}
|
|
}
|
|
topicName = event.sns.topicName || topicName
|
|
// Only true when providing CFN intrinsic function (not string) to arn property without topicName property
|
|
if (!topicName) {
|
|
throw new ServerlessError(
|
|
'Missing "sns.topicName" setting (it needs to be provided if "sns.arn" is not provided as plain ARN string)',
|
|
'SNS_MISSING_TOPIC_NAME',
|
|
)
|
|
}
|
|
} else {
|
|
topicName = event.sns.topicName
|
|
}
|
|
} else if (event.sns.indexOf('arn:') === 0) {
|
|
topicArn = event.sns
|
|
const splitArn = topicArn.split(':')
|
|
topicName = splitArn[splitArn.length - 1]
|
|
} else {
|
|
topicName = event.sns
|
|
}
|
|
|
|
if (event.sns.redrivePolicy) {
|
|
const {
|
|
deadLetterTargetArn,
|
|
deadLetterTargetRef,
|
|
deadLetterTargetImport,
|
|
} = event.sns.redrivePolicy
|
|
let targetArn
|
|
let targetUrl
|
|
|
|
if (deadLetterTargetArn) {
|
|
targetArn = deadLetterTargetArn
|
|
// arn:aws:sqs:us-east-1:11111111111:myDLQ
|
|
const [deQueueName, deAccount, deRegion] = deadLetterTargetArn
|
|
.split(':')
|
|
.reverse()
|
|
targetUrl = {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
`https://sqs.${deRegion}.`,
|
|
{ Ref: 'AWS::URLSuffix' },
|
|
`/${deAccount}/${deQueueName}`,
|
|
],
|
|
],
|
|
}
|
|
} else if (deadLetterTargetRef) {
|
|
targetArn = {
|
|
'Fn::GetAtt': [deadLetterTargetRef, 'Arn'],
|
|
}
|
|
targetUrl = {
|
|
Ref: deadLetterTargetRef,
|
|
}
|
|
} else {
|
|
targetArn = {
|
|
'Fn::ImportValue': deadLetterTargetImport.arn,
|
|
}
|
|
targetUrl = {
|
|
'Fn::ImportValue': deadLetterTargetImport.url,
|
|
}
|
|
}
|
|
|
|
redrivePolicy = {
|
|
deadLetterTargetArn: targetArn,
|
|
}
|
|
|
|
const queuePolicyLogicalId =
|
|
this.provider.naming.getTopicDLQPolicyLogicalId(
|
|
functionName,
|
|
topicName,
|
|
)
|
|
|
|
Object.assign(template.Resources, {
|
|
[queuePolicyLogicalId]: {
|
|
Type: 'AWS::SQS::QueuePolicy',
|
|
Properties: {
|
|
PolicyDocument: {
|
|
Version: '2012-10-17',
|
|
Id: queuePolicyLogicalId,
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Principal: {
|
|
Service: 'sns.amazonaws.com',
|
|
},
|
|
Action: 'sqs:SendMessage',
|
|
Resource: targetArn,
|
|
Condition: {
|
|
ArnEquals: {
|
|
'aws:SourceArn': topicArn,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
Queues: [targetUrl],
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const endpoint = resolveLambdaTarget(functionName, functionObj)
|
|
|
|
const subscriptionLogicalId =
|
|
this.provider.naming.getLambdaSnsSubscriptionLogicalId(
|
|
functionName,
|
|
topicName,
|
|
)
|
|
|
|
if (topicArn) {
|
|
_.merge(template.Resources, {
|
|
[subscriptionLogicalId]: {
|
|
Type: 'AWS::SNS::Subscription',
|
|
DependsOn: _.get(functionObj.targetAlias, 'logicalId'),
|
|
Properties: {
|
|
TopicArn: topicArn,
|
|
Protocol: 'lambda',
|
|
Endpoint: endpoint,
|
|
FilterPolicy: event.sns.filterPolicy,
|
|
FilterPolicyScope: event.sns.filterPolicyScope,
|
|
RedrivePolicy: redrivePolicy,
|
|
Region: region,
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
topicArn = {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':sns:',
|
|
{ Ref: 'AWS::Region' },
|
|
':',
|
|
{ Ref: 'AWS::AccountId' },
|
|
':',
|
|
topicName,
|
|
],
|
|
],
|
|
}
|
|
|
|
const topicLogicalId =
|
|
this.provider.naming.getTopicLogicalId(topicName)
|
|
|
|
const subscription = {
|
|
Endpoint: endpoint,
|
|
Protocol: 'lambda',
|
|
}
|
|
|
|
if (!(topicLogicalId in template.Resources)) {
|
|
const topicResourceProperties = { TopicName: topicName }
|
|
if (event.sns.displayName) {
|
|
topicResourceProperties.DisplayName = event.sns.displayName
|
|
}
|
|
_.merge(template.Resources, {
|
|
[topicLogicalId]: {
|
|
Type: 'AWS::SNS::Topic',
|
|
DependsOn: _.get(functionObj.targetAlias, 'logicalId'),
|
|
Properties: topicResourceProperties,
|
|
},
|
|
})
|
|
}
|
|
|
|
if (event.sns.filterPolicy || redrivePolicy) {
|
|
_.merge(template.Resources, {
|
|
[subscriptionLogicalId]: {
|
|
Type: 'AWS::SNS::Subscription',
|
|
Properties: _.merge(subscription, {
|
|
TopicArn: {
|
|
Ref: topicLogicalId,
|
|
},
|
|
FilterPolicy: event.sns.filterPolicy,
|
|
FilterPolicyScope: event.sns.filterPolicyScope,
|
|
RedrivePolicy: redrivePolicy,
|
|
}),
|
|
},
|
|
})
|
|
} else {
|
|
if (
|
|
!template.Resources[topicLogicalId].Properties.Subscription
|
|
) {
|
|
template.Resources[topicLogicalId].Properties.Subscription =
|
|
[]
|
|
}
|
|
template.Resources[topicLogicalId].Properties.Subscription.push(
|
|
subscription,
|
|
)
|
|
}
|
|
}
|
|
|
|
const lambdaPermissionLogicalId =
|
|
this.provider.naming.getLambdaSnsPermissionLogicalId(
|
|
functionName,
|
|
topicName,
|
|
)
|
|
|
|
_.merge(template.Resources, {
|
|
[lambdaPermissionLogicalId]: {
|
|
Type: 'AWS::Lambda::Permission',
|
|
DependsOn: _.get(functionObj.targetAlias, 'logicalId'),
|
|
Properties: {
|
|
FunctionName: endpoint,
|
|
Action: 'lambda:InvokeFunction',
|
|
Principal: 'sns.amazonaws.com',
|
|
SourceArn: topicArn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
export default AwsCompileSNSEvents
|