From 2603042d03fe94fec9f3fe7624c561b204024047 Mon Sep 17 00:00:00 2001 From: richardhill Date: Mon, 8 Jun 2020 19:13:57 +0100 Subject: [PATCH] Adding support for FIFO queues along with message deduplication support --- fifo-queue-template.yml | 96 +++++++++++++++++++++ index.js | 28 +++++- template.yml => standard-queue-template.yml | 2 +- 3 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 fifo-queue-template.yml rename template.yml => standard-queue-template.yml (95%) diff --git a/fifo-queue-template.yml b/fifo-queue-template.yml new file mode 100644 index 0000000..7058830 --- /dev/null +++ b/fifo-queue-template.yml @@ -0,0 +1,96 @@ +Resources: + SQSQueue: + Type: "AWS::SQS::Queue" + Properties: + QueueName: STAGE_NAME-QUEUE_NAME + FifoQueue: FIFO_TYPE + ContentBasedDeduplication: CONTENT_BASED_DEDUPLICATION + + ProxyApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: STAGE_NAME-QUEUE_NAME-ApiGateway + + APIGatewayResource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: + Fn::GetAtt: + - "ProxyApi" + - "RootResourceId" + PathPart: "API_ENDPOINT" + RestApiId: + Ref: ProxyApi + + SQSAPIMethod: + Type: AWS::ApiGateway::Method + DependsOn: SQSQueue + Properties: + RestApiId: + Ref: ProxyApi + ResourceId: + Ref: APIGatewayResource + HttpMethod: "POST" + MethodResponses: + - + StatusCode: "200" + ResponseParameters: + "method.response.header.Access-Control-Allow-Origin": true + AuthorizationType: "NONE" + Integration: + Type: AWS + Credentials: !Sub "${APIGatewaySQSIAM.Arn}" + RequestParameters: + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + IntegrationHttpMethod: POST + RequestTemplates: + "application/json": "Action=SendMessage&MessageBody=$input.body&MessageDeduplicationId=$input.params('MessageDeduplicationId')&MessageGroupId=$input.params('MessageGroupId')" + PassthroughBehavior: Never + IntegrationResponses: + - + StatusCode: "200" + ResponseParameters: + "method.response.header.Access-Control-Allow-Origin": "'*'" + ResponseTemplates: + "application/json": "" + Uri: !Sub arn:aws:apigateway:us-east-1:sqs:path/${AWS::AccountId}/${SQSQueue.QueueName} + + APIGatewayDeployment: + Type: AWS::ApiGateway::Deployment + DependsOn: SQSAPIMethod + Properties: + RestApiId: !Sub "${ProxyApi}" + StageName: STAGE_NAME + + APIGatewaySQSIAM: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - apigateway.amazonaws.com + Action: sts:AssumeRole + Policies: + - + PolicyName: STAGE_NAME-QUEUE_NAME-policy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Resource: "*" + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - Effect: Allow + Resource: + - !Sub "${SQSQueue.Arn}" + Action: + - "sqs:SendMessage" + +Outputs: + ApiGwUrl: + Value: !Sub "https://${ProxyApi}.execute-api.${AWS::Region}.amazonaws.com/STAGE_NAME/API_ENDPOINT" \ No newline at end of file diff --git a/index.js b/index.js index 5271044..30cbce1 100644 --- a/index.js +++ b/index.js @@ -55,17 +55,29 @@ class ServerlessApiGWSqsPlugin { } beforeDeployResources() { this.setCloudFormation() + + const fifoQueueYaml = 'fifo-queue-template.yml'; + const standardQueueYaml = 'standard-queue-template.yml'; + const templateType = this.serverless.service.custom.apiGwSqs.fifoQueue ? fifoQueueYaml :standardQueueYaml; + return new Promise((resolve, reject) => { - fs.readFile(__dirname + '/' + 'template.yml', 'utf8', (err, contents) => { + fs.readFile(__dirname + '/' + templateType , 'utf8', (err, contents) => { var stackName = this.getStackName(this.options.stage, this.serverless.service.service) var apiEndpoint = this.serverless.service.custom.apiGwSqs.apiEndpoint var queueName = this.serverless.service.custom.apiGwSqs.queueName + var fifoQueueType = this.serverless.service.custom.apiGwSqs.fifoQueue + var contentBasedDeduplication = this.serverless.service.custom.apiGwSqs.contentBasedDeduplication var replaceStageName = new RegExp('STAGE_NAME', 'g'); var replaceApiEndpoint = new RegExp('API_ENDPOINT', 'g'); var replaceQueueName = new RegExp('QUEUE_NAME', 'g'); + var replaceFifoType = new RegExp('FIFO_TYPE', 'g'); + var replaceContentDeduplication = new RegExp('CONTENT_BASED_DEDUPLICATION') contents = contents.replace(replaceStageName, this.options.stage); contents = contents.replace(replaceApiEndpoint, apiEndpoint); contents = contents.replace(replaceQueueName, queueName); + contents = contents.replace(replaceFifoType, fifoQueueType); + contents = contents.replace(replaceContentDeduplication, contentBasedDeduplication); + var params = { Capabilities: [ 'CAPABILITY_IAM' @@ -73,10 +85,18 @@ class ServerlessApiGWSqsPlugin { StackName: stackName, TemplateBody: contents, }; - if (queueName.includes(".")) { - console.log("[CodeRecipe ApiGW SQS Plugin] QueueName Error: Can only include alphanumeric characters, hyphens, or underscores. 1 to 80 in length") - reject() + + const fifoName = '.fifo'; + if (fifoQueueType && !queueName.endsWith(fifoName)) { + console.log("[CodeRecipe ApiGW SQS Plugin] QueueName Error: Fifo Queues MUST end in '.fifo' eg 'testQueue.fifo'. Remember to only include alphanumeric characters, hyphens, or underscores. 1 to 80 in length"); + reject(); } + + if (fifoQueueType === undefined && queueName.includes(".")) { + console.log("[CodeRecipe ApiGW SQS Plugin] QueueName Error: Can only include alphanumeric characters, hyphens, or underscores. 1 to 80 in length"); + reject(); + } + this.cloudformation.createStack(params, (err, data) => { if (err) { if(err.code == 'AlreadyExistsException') { diff --git a/template.yml b/standard-queue-template.yml similarity index 95% rename from template.yml rename to standard-queue-template.yml index 83c1552..272e109 100644 --- a/template.yml +++ b/standard-queue-template.yml @@ -42,7 +42,7 @@ Resources: "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" IntegrationHttpMethod: POST RequestTemplates: - "application/json": "Action=SendMessage&MessageBody=$input.body" + "application/json": "Action=SendMessage&MessageBody=$input.body&MessageDeduplicationId=$input.params('MessageDeduplicationId')&MessageGroupId=$input.params('MessageGroupId')" PassthroughBehavior: Never IntegrationResponses: -