The following terraform script allows us to have incoming api gateway requests be enqueued onto sqs for late processing.
locals {
api_gateway_name = "order-status-webhooks-gateway"
}
data "aws_region" "current" {}
resource "aws_iam_role" "api" {
name = local.api_gateway_name
tags = {
"name" = local.api_gateway_name
}
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "api" {
name = local.api_gateway_name
tags = {
"name" = local.api_gateway_name
}
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"sqs:GetQueueUrl",
"sqs:ChangeMessageVisibility",
"sqs:ListDeadLetterSourceQueues",
"sqs:SendMessageBatch",
"sqs:PurgeQueue",
"sqs:ReceiveMessage",
"sqs:SendMessage",
"sqs:GetQueueAttributes",
"sqs:CreateQueue",
"sqs:ListQueueTags",
"sqs:ChangeMessageVisibilityBatch",
"sqs:SetQueueAttributes"
],
"Resource": "${aws_sqs_queue.queue.arn}"
},
{
"Effect": "Allow",
"Action": "sqs:ListQueues",
"Resource": "*"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "api" {
role = "${aws_iam_role.api.name}"
policy_arn = "${aws_iam_policy.api.arn}"
}
resource "aws_api_gateway_rest_api" "api" {
name = local.api_gateway_name
description = "dragontail uses this api as a webhook endpoint to send us updates on orders"
tags = {
"name" = local.api_gateway_name
}
}
resource "aws_api_gateway_resource" "dragontail" {
path_part = "dragontail"
parent_id = aws_api_gateway_rest_api.api.root_resource_id
rest_api_id = aws_api_gateway_rest_api.api.id
}
resource "aws_api_gateway_resource" "order_status" {
path_part = "order-status"
parent_id = aws_api_gateway_resource.dragontail.id
rest_api_id = aws_api_gateway_rest_api.api.id
}
resource "aws_api_gateway_method" "api" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.order_status.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_usage_plan" "usage_plan" {
name = local.api_gateway_name
api_stages {
api_id = aws_api_gateway_rest_api.api.id
stage = aws_api_gateway_stage.prod.stage_name
}
tags = {
"name" = local.api_gateway_name
}
}
resource "aws_api_gateway_integration" "api" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.order_status.id}"
http_method = "POST"
type = "AWS"
integration_http_method = "POST"
passthrough_behavior = "NEVER"
credentials = "${aws_iam_role.api.arn}"
uri = "arn:aws:apigateway:${data.aws_region.current.name}:sqs:path/${aws_sqs_queue.queue.name}"
request_parameters = {
"integration.request.header.Content-Type" = "'application/x-www-form-urlencoded'"
}
request_templates = {
"application/json" = "${file("sqs-integration-request-mapping.template")}"
}
/*
# Alternative way of adding body and message attributes to SQS event
request_templates = {
# tells to pass http body as sqs message body and http request path
# as an attribute called 'path' and 'sourceIp' as these will be used in the lambda
"application/json" = join("&",
[
"Action=SendMessage",
"MessageBody=$input.body",
"MessageAttribute.1.Name=path",
"MessageAttribute.1.Value.StringValue=$util.urlEncode($context.path)",
"MessageAttribute.1.Value.DataType=String",
"MessageAttribute.2.Name=sourceIp",
"MessageAttribute.2.Value.StringValue=$util.urlEncode($context.identity.sourceIp)",
"MessageAttribute.2.Value.DataType=String"
])
}
*/
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_integration_response" "response_200" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.order_status.id}"
http_method = "${aws_api_gateway_method.api.http_method}"
status_code = "${aws_api_gateway_method_response.response_200.status_code}"
selection_pattern = "^2[0-9][0-9]" // regex pattern for any 200 message that comes back from SQS
response_templates = {
"application/json" = "{\"message\": \"enqueued for processing.\"}"
}
depends_on = [
aws_api_gateway_integration.api
]
}
resource "aws_api_gateway_method_response" "response_200" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.order_status.id}"
http_method = "${aws_api_gateway_method.api.http_method}"
status_code = 200
response_models = {
"application/json" = "Empty"
}
}
resource "aws_api_gateway_stage" "prod" {
stage_name = "prod"
rest_api_id = aws_api_gateway_rest_api.api.id
deployment_id = aws_api_gateway_deployment.deployment.id
lifecycle {
create_before_destroy = true
}
tags = {
"name" = local.api_gateway_name
}
}
resource "aws_api_gateway_deployment" "deployment" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
depends_on = [
aws_api_gateway_integration.api,
aws_api_gateway_method.api,
]
/*
terraform is not able to detect certain changes and will cause api gateway to use the old componenets
in order to force it to detect new tf changes the following hack is used, more info at:
https://github.com/hashicorp/terraform/issues/6613#issuecomment-322264393
*/
triggers = {
redeployment = join(",",[
filemd5("sqs-integration-request-mapping.template"),
filemd5("api-gateway.tf")
])
}
lifecycle {
create_before_destroy = true
}
}
And the request template:
Action=SendMessage&MessageBody={
"requestBody" : $input.json('$'),
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"context": {
"path": $util.urlEncode($context.path),
"sourceIp": $util.urlEncode($context.identity.sourceIp)
}
}