Skip to content

Commit

Permalink
CDD-2443 Create api gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristianAMartin committed Jan 29, 2025
1 parent a223e4f commit 1fc0e12
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 9 deletions.
13 changes: 13 additions & 0 deletions terraform/20-app/api-gateway.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module "api_gateway" {
source = "../modules/api-gateway"
name = "app-${local.prefix}-api-gateway"
description = "API Gateway for ${local.prefix}"
api_gateway_stage_name = var.api_gateway_stage_name
lambda_role_arn = module.cognito.cognito_lambda_role_arn
cognito_user_pool_arn = module.cognito.cognito_user_pool_arn
region = local.region
resource_path_part = "{proxy+}"
lambda_invoke_arn = module.api_gateway.lambda_alias_arn
lambda_function_arn = module.api_gateway.api_gateway_lambda_arn
prefix = local.prefix
}
2 changes: 1 addition & 1 deletion terraform/20-app/cognito.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
resource "aws_iam_role" "cognito_sns_role" {
name = "cognito-sns-role-${terraform.workspace}"
name = "app-${local.prefix}-cognito-sns-role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Expand Down
6 changes: 6 additions & 0 deletions terraform/20-app/vars.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ variable "single_nat_gateway" {
}

variable "halo_account_type" {}

variable "api_gateway_stage_name" {
description = "The stage name for API Gateway (e.g. dev or live)"
type = string
default = "dev"
}
11 changes: 11 additions & 0 deletions terraform/modules/api-gateway/api_gateway_lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports.handler = async (event) => {
console.log("Received event:", JSON.stringify(event, null, 2));

return {
statusCode: 200,
body: JSON.stringify({ message: "Hello from API Gateway via Lambda!" }),
headers: {
"Content-Type": "application/json"
}
};
};
Binary file added terraform/modules/api-gateway/api_gateway_lambda.zip
Binary file not shown.
183 changes: 183 additions & 0 deletions terraform/modules/api-gateway/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
data "aws_caller_identity" "current" {}

resource "aws_lambda_function" "api_gateway_lambda" {
function_name = "app-${var.prefix}-api-gateway-lambda"
runtime = "nodejs18.x"
role = var.lambda_role_arn
handler = "index.handler"

source_code_hash = filebase64sha256("${path.module}/api_gateway_lambda.zip")
filename = "${path.module}/api_gateway_lambda.zip"
timeout = 15
publish = true
}

resource "aws_lambda_alias" "live" {
name = "live"
description = "Alias pointing to the live version of the Lambda function"
function_name = aws_lambda_function.api_gateway_lambda.arn
function_version = "$LATEST"
}

resource "aws_lambda_alias" "dev" {
name = "dev"
description = "Alias pointing to the dev version of the Lambda function"
function_name = aws_lambda_function.api_gateway_lambda.arn
function_version = "$LATEST"
}

resource "aws_api_gateway_rest_api" "api_gateway" {
name = var.name
description = var.description
}

resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
parent_id = aws_api_gateway_rest_api.api_gateway.root_resource_id
path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "COGNITO_USER_POOLS"
authorizer_id = aws_api_gateway_authorizer.cognito.id
}

resource "aws_api_gateway_authorizer" "cognito" {
name = "app-${var.prefix}-cognito-authorizer"
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
type = "COGNITO_USER_POOLS"
provider_arns = [var.cognito_user_pool_arn]
}

resource "aws_api_gateway_integration" "proxy_integration" {
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/${lookup({
"live" = aws_lambda_alias.live.arn,
"dev" = aws_lambda_alias.dev.arn
}, var.lambda_alias, aws_lambda_alias.live.arn)}/invocations"
}

resource "aws_api_gateway_deployment" "deployment" {
depends_on = [
aws_api_gateway_method.proxy,
aws_api_gateway_integration.proxy_integration
]
rest_api_id = aws_api_gateway_rest_api.api_gateway.id

triggers = {
redeployment = timestamp()
}

lifecycle {
create_before_destroy = true
}
}

resource "aws_api_gateway_stage" "stage" {
stage_name = var.api_gateway_stage_name
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
deployment_id = aws_api_gateway_deployment.deployment.id

description = "Stage for ${var.api_gateway_stage_name}"
variables = {
lambda_alias = var.lambda_alias
}

lifecycle {
prevent_destroy = false
}
}

resource "aws_api_gateway_account" "account" {
cloudwatch_role_arn = aws_iam_role.api_gateway_cloudwatch_role.arn

depends_on = [
aws_iam_role.api_gateway_cloudwatch_role,
aws_iam_role_policy.api_gateway_cloudwatch_policy
]
}

resource "aws_iam_role" "api_gateway_cloudwatch_role" {
name = "app-${var.prefix}-api-gateway-cloudwatch-role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "apigateway.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_role_policy" "api_gateway_cloudwatch_policy" {
role = aws_iam_role.api_gateway_cloudwatch_role.id

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
Resource = [
"arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/*"
]
},
{
Effect = "Allow",
Action = [
"apigateway:GET",
"apigateway:PUT",
"apigateway:POST",
"apigateway:DELETE",
"apigateway:PATCH"
],
Resource = aws_api_gateway_rest_api.api_gateway.execution_arn
}
]
})
}

resource "aws_lambda_permission" "allow_api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = lookup({
"live" = aws_lambda_alias.live.arn,
"dev" = aws_lambda_alias.dev.arn
}, var.lambda_alias, aws_lambda_alias.live.arn)
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${var.region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.api_gateway.id}/*/*/*"
}

output "api_gateway_lambda_arn" {
description = "The ARN of the API Gateway Lambda function"
value = aws_lambda_function.api_gateway_lambda.arn
}

variable "prefix" {
description = "Prefix for naming resources"
type = string
validation {
condition = can(regex("^[a-zA-Z0-9_-]+$", var.prefix))
error_message = "Prefix must only contain letters, numbers, hyphens, or underscores."
}
}
27 changes: 27 additions & 0 deletions terraform/modules/api-gateway/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
output "api_gateway_id" {
description = "The ID of the API Gateway"
value = aws_api_gateway_rest_api.api_gateway.id
}

output "api_gateway_url" {
description = "The invoke URL of the API Gateway"
value = aws_api_gateway_deployment.deployment.invoke_url
}

output "api_gateway_stage_name" {
description = "The stage name of the API Gateway deployment"
value = aws_api_gateway_stage.stage.stage_name
}

output "api_gateway_lambda_invoke_arn" {
description = "The invoke ARN for the API Gateway Lambda function alias"
value = var.lambda_alias == "live" ? aws_lambda_alias.live.arn : aws_lambda_alias.dev.arn
}

output "lambda_alias_arn" {
description = "The ARN of the selected Lambda alias"
value = lookup({
"live" = aws_lambda_alias.live.arn,
"dev" = aws_lambda_alias.dev.arn
}, var.lambda_alias, aws_lambda_alias.live.arn)
}
103 changes: 103 additions & 0 deletions terraform/modules/api-gateway/vars.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
variable "name" {
description = "The name of the API Gateway"
type = string

validation {
condition = length(var.name) > 0
error_message = "The 'name' variable must be a non-empty string."
}
}

variable "description" {
description = "The description of the API Gateway"
type = string
default = "API Gateway for the application"

validation {
condition = length(var.description) > 0
error_message = "The 'description' variable must be a non-empty string."
}
}

variable "api_gateway_stage_name" {
description = "The stage name for API Gateway (e.g. dev or live)"
type = string
default = "dev"

validation {
condition = can(regex("^[a-zA-Z0-9_-]+$", var.api_gateway_stage_name))
error_message = "The 'stage_name' variable must only contain alphanumeric characters, dashes, or underscores."
}
}

variable "lambda_function_arn" {
description = "The ARN of the Lambda function to integrate with API Gateway"
type = string

validation {
condition = can(regex("^arn:aws:lambda:.*:.*:function:.*$", var.lambda_function_arn))
error_message = "The 'lambda_function_arn' must be a valid Lambda function ARN."
}
}

variable "cognito_user_pool_arn" {
description = "The ARN of the Cognito User Pool for authorizing requests"
type = string

validation {
condition = can(regex("^arn:aws:cognito-idp:.*:.*:userpool/.*$", var.cognito_user_pool_arn))
error_message = "The 'cognito_user_pool_arn' must be a valid Cognito User Pool ARN."
}
}

variable "region" {
description = "The AWS region for resources"
type = string

validation {
condition = can(regex("^[a-z]{2}-[a-z]+-[0-9]{1}$", var.region))
error_message = "The 'region' variable must be a valid AWS region string, e.g. 'eu-west-2'."
}
}

variable "resource_path_part" {
description = "The resource path part for API Gateway (e.g. 'data' or '{proxy+}')"
type = string
default = "{proxy+}"

validation {
condition = can(regex("^[a-zA-Z0-9/{}+_-]+$", var.resource_path_part))
error_message = "The 'resource_path_part' must be a valid path segment containing alphanumeric characters, slashes, curly braces, dashes, or underscores."
}
}

variable "lambda_invoke_arn" {
description = "The ARN of the Lambda function for API Gateway"
type = string

validation {
condition = can(regex("^arn:aws:lambda:.*:.*:function:.*$", var.lambda_invoke_arn))
error_message = "The 'lambda_invoke_arn' must be a valid Lambda function ARN."
}
}

variable "lambda_role_arn" {
description = "IAM Role ARN for the Lambda function"
type = string

validation {
condition = can(regex("^arn:aws:iam::\\d{12}:role/.+$", var.lambda_role_arn))
error_message = "The 'lambda_role_arn' must be a valid IAM Role ARN."
}
}

variable "lambda_alias" {
description = "The alias for the Lambda function (e.g. live, dev)"
type = string
default = "live"

validation {
condition = contains(["dev", "live"], var.lambda_alias)
error_message = "Invalid alias provided. Allowed values are 'dev' or 'live'."
}
}
Loading

0 comments on commit 1fc0e12

Please sign in to comment.