From e5ab0a576da813b0178bea8a0e9a2e075f5f1964 Mon Sep 17 00:00:00 2001 From: Austin Davis Date: Mon, 5 Feb 2024 01:51:44 -0700 Subject: [PATCH] api gateway v1 -> v2 and now uses openapi spec for setup --- openapi.json | 43 ++++++++++ proxy/main.go | 3 +- terraform/api-gateway/main.tf | 130 +++++++++++++++++-------------- terraform/api-gateway/outputs.tf | 3 +- terraform/api-gateway/vars.tf | 17 ++-- terraform/main.tf | 5 +- 6 files changed, 129 insertions(+), 72 deletions(-) create mode 100644 openapi.json diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..cbe941b --- /dev/null +++ b/openapi.json @@ -0,0 +1,43 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Proxy API", + "version": "1.0" + }, + "paths" : { + "/headless" : { + "get" : { + "responses" : { + "default" : { + "description" : "Default response for GET /headless" + } + }, + "x-amazon-apigateway-integration" : { + "payloadFormatVersion" : "2.0", + "type" : "aws_proxy", + "httpMethod" : "POST", + "uri" : "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:{account-id}:function:headless-default/invocations", + "connectionType" : "INTERNET", + "credentials": "arn:aws:iam::{account-id}:role/{iam-role}" + } + } + }, + "/proxy" : { + "get" : { + "responses" : { + "default" : { + "description" : "Default response for GET /proxy" + } + }, + "x-amazon-apigateway-integration" : { + "payloadFormatVersion" : "2.0", + "type" : "aws_proxy", + "httpMethod" : "POST", + "uri" : "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:{account-id}:function:proxy-default/invocations", + "connectionType" : "INTERNET", + "credentials": "arn:aws:iam::{account-id}:role/{iam-role}" + } + } + } + } +} \ No newline at end of file diff --git a/proxy/main.go b/proxy/main.go index 3cec6ac..4d20495 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -33,9 +33,8 @@ func getPageHtml(url string) (string, error) { return string(body), nil } -func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { +func handler(ctx context.Context, request events.APIGatewayV2HTTPRequest) (events.APIGatewayProxyResponse, error) { url := request.QueryStringParameters["url"] - body, err := getPageHtml(url) if err != nil { return events.APIGatewayProxyResponse{ diff --git a/terraform/api-gateway/main.tf b/terraform/api-gateway/main.tf index 866a4de..2cbc9ca 100644 --- a/terraform/api-gateway/main.tf +++ b/terraform/api-gateway/main.tf @@ -1,74 +1,90 @@ -resource "aws_api_gateway_rest_api" "api_source" { - name = "${var.api_name}" +resource "aws_iam_role" "api_gateway_role" { + name = "${var.api_name}-api-gateway-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "apigateway.amazonaws.com" + } + }, + ] + }) } -resource "aws_api_gateway_resource" "api_path" { - rest_api_id = aws_api_gateway_rest_api.api_source.id - parent_id = aws_api_gateway_rest_api.api_source.root_resource_id - path_part = "${var.route}" +resource "aws_iam_role_policy" "api_gateway_policy" { + name = "${var.api_name}-api-gateway-policy" + role = aws_iam_role.api_gateway_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "lambda:InvokeFunction" + ] + Effect = "Allow" + Resource = var.lambda_arns + }, + ] + }) } -resource "aws_api_gateway_method" "api_method" { - rest_api_id = aws_api_gateway_rest_api.api_source.id - resource_id = aws_api_gateway_resource.api_path.id - http_method = "GET" - authorization = "NONE" +data "aws_caller_identity" "current" {} + + +# This will be needed to replace the account-id/iam-role in the OpenAPI definition +data "local_file" "openapi" { + filename = var.openapi } -resource "aws_api_gateway_integration" "api_integration" { - rest_api_id = aws_api_gateway_rest_api.api_source.id - resource_id = aws_api_gateway_resource.api_path.id - http_method = aws_api_gateway_method.api_method.http_method - integration_http_method = "POST" - type = "AWS_PROXY" - uri = "${var.lambda_invoke_arn}" + +resource "aws_apigatewayv2_api" "api" { + name = var.api_name + protocol_type = "HTTP" + # replaces the placeholders in the OpenAPI definition with the actual values + body = replace( + replace( + data.local_file.openapi.content, + "{account-id}", + data.aws_caller_identity.current.account_id + ), + "{iam-role}", + aws_iam_role.api_gateway_role.name + ) + } -resource "aws_api_gateway_deployment" "api_deployment" { - depends_on = [aws_api_gateway_integration.api_integration] - rest_api_id = aws_api_gateway_rest_api.api_source.id - stage_name = "prod" - triggers = { +resource "aws_apigatewayv2_deployment" "api_deployment" { + api_id = aws_apigatewayv2_api.api.id + description = "${var.api_name} deployment" - # NOTE: The configuration below will satisfy ordering considerations, - # but not pick up all future REST API changes. More advanced patterns - # are possible, such as using the filesha1() function against the - # Terraform configuration file(s) or removing the .id references to - # calculate a hash against whole resources. Be aware that using whole - # resources will show a difference after the initial implementation. - # It will stabilize to only change when resources change afterwards. - redeployment = sha1(jsonencode([ - aws_api_gateway_resource.api_path.id, - aws_api_gateway_method.api_method.id, - aws_api_gateway_integration.api_integration.id, - ])) + lifecycle { + create_before_destroy = true } } -resource "aws_api_gateway_method_settings" "general_settings" { - rest_api_id = "${aws_api_gateway_rest_api.api_source.id}" - stage_name = "${aws_api_gateway_deployment.api_deployment.stage_name}" - method_path = "*/*" - - settings { - # Enable CloudWatch logging and metrics - metrics_enabled = true - data_trace_enabled = true - logging_level = "INFO" +resource "aws_cloudwatch_log_group" "api_log_group" { + name = "${var.api_name}-logs" +} - # Limit the rate of calls to prevent abuse and unwanted charges - throttling_rate_limit = 100 +resource "aws_apigatewayv2_stage" "api_stage" { + api_id = aws_apigatewayv2_api.api.id + name = "prod" + description = "Production stage" + auto_deploy = true + default_route_settings { + logging_level = "INFO" throttling_burst_limit = 50 + throttling_rate_limit = 100 + } + + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_log_group.arn + format = "$context.identity.sourceIp - - [$context.requestTime] \"$context.httpMethod $context.routeKey $context.protocol\" $context.status $context.responseLength $context.requestId $context.error.message $context.integration.error" } } -resource "aws_lambda_permission" "lambda_permission" { - statement_id = "AllowMyAPIInvoke" - action = "lambda:InvokeFunction" - function_name = "${var.lambda_name}" - principal = "apigateway.amazonaws.com" - - # The /* part allows invocation from any stage, method and resource path - # within API Gateway. - source_arn = "${aws_api_gateway_rest_api.api_source.execution_arn}/*" -} \ No newline at end of file diff --git a/terraform/api-gateway/outputs.tf b/terraform/api-gateway/outputs.tf index 894dfc3..4887fd2 100644 --- a/terraform/api-gateway/outputs.tf +++ b/terraform/api-gateway/outputs.tf @@ -1,4 +1,5 @@ output "api_url" { description = "The URL of the API Gateway" - value = aws_api_gateway_deployment.api_deployment.invoke_url + value = aws_apigatewayv2_stage.api_stage.invoke_url + sensitive = true } \ No newline at end of file diff --git a/terraform/api-gateway/vars.tf b/terraform/api-gateway/vars.tf index 7b85ad2..ae0f6f7 100644 --- a/terraform/api-gateway/vars.tf +++ b/terraform/api-gateway/vars.tf @@ -1,15 +1,14 @@ -variable "lambda_name" { - description = "name of the lambda to attach to the gateway" +variable "api_name" { + description = "arn of the lambda to attach to the gateway" } -variable "lambda_invoke_arn" { - description = "invoke_arn of the lambda to attach to the gateway" +variable "openapi" { + description = "The OpenAPI definition" } -variable "api_name" { - description = "arn of the lambda to attach to the gateway" +variable "lambda_arns" { + description = "Array of Lambda ARNs" + type = list(string) + default = [] } -variable "route"{ - description = "route of the api to attach to the lambda" -} \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf index 070545d..599db41 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -74,8 +74,7 @@ module "scraper_lambda_trigger" { module "proxy_gateway" { source = "./api-gateway" - lambda_name = "${module.proxy_lambda.name}" - lambda_invoke_arn = "${module.proxy_lambda.invoke_arn}" api_name = "proxy-${terraform.workspace}" - route = "proxy" + openapi = "../openapi.json" + lambda_arns = [ module.headless_lambda.arn, module.proxy_lambda.arn] } \ No newline at end of file