From 98767d47a1f7cb4ef1b6af5a18c9383cc19e4c1a Mon Sep 17 00:00:00 2001 From: BEW111 Date: Sun, 18 Aug 2024 13:02:52 -0400 Subject: [PATCH 1/2] add separate backend tf --- infrastructure/backend/.terraform.lock.hcl | 25 +++ infrastructure/backend/main.tf | 218 +++++++++++++++++++++ infrastructure/backend/outputs.tf | 4 + infrastructure/backend/variables.tf | 53 +++++ 4 files changed, 300 insertions(+) create mode 100644 infrastructure/backend/.terraform.lock.hcl create mode 100644 infrastructure/backend/main.tf create mode 100644 infrastructure/backend/outputs.tf create mode 100644 infrastructure/backend/variables.tf diff --git a/infrastructure/backend/.terraform.lock.hcl b/infrastructure/backend/.terraform.lock.hcl new file mode 100644 index 00000000..0fa7c0ce --- /dev/null +++ b/infrastructure/backend/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.67.0" + constraints = "~> 4.16" + hashes = [ + "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", + "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", + "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", + "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", + "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", + "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", + "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", + "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", + "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", + "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", + "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", + "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", + "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", + "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + ] +} diff --git a/infrastructure/backend/main.tf b/infrastructure/backend/main.tf new file mode 100644 index 00000000..e59c7373 --- /dev/null +++ b/infrastructure/backend/main.tf @@ -0,0 +1,218 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + required_version = ">= 1.2.0" +} + +provider "aws" { + region = var.region +} + +locals { + server_image_tag = "ghcr.io/${var.github_repo_owner}/${var.github_repo_name}:server" +} + +resource "aws_ecs_cluster" "cluster" { + name = var.cluster_name + setting { + name = "containerInsights" + value = "enabled" + } +} + +# TODO: this only needs to be created once; should we keep it in here or create +# it manually? +# resource "aws_cloudwatch_log_group" "ecs_app_family_log_group" { +# name = "/ecs/app-family" +# // retention_in_days = 90 +# } + +resource "aws_ecs_task_definition" "app" { + family = "app-family" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "256" + memory = "1024" + execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn + container_definitions = jsonencode([ + { + name = "backend" + image = local.server_image_tag + cpu = 256 + memory = 1024 + essential = true + portMappings = [ + { + containerPort = 4000 + hostPort = 4000 + protocol = "tcp" + } + ], + environment = [ + // TODO: we may end up needing to have two env vars here, one for the + // alb url (for cors), and one for the redirect (the actual domain name) + // - Right now we're just using `FRONTEND_URL` in the same places in + // the backend + { "name" : "FRONTEND_URL", "value" : "http://localhost:3000" }, + { "name" : "ATLAS_URI", "value" : var.atlas_uri }, + { "name" : "COOKIE_SECRET", "value" : var.cookie_secret }, + { "name" : "SENDGRID_API_KEY", "value" : var.sendgrid_api_key }, + { "name" : "SENDGRID_EMAIL_ADDRESS", "value" : var.sendgrid_email_address } + ], + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = "/ecs/app-family" + awslogs-region = var.region + awslogs-stream-prefix = "backend" + } + } + } + ]) +} + +// VPC configuration +// TODO: decide if we should use a different VPC +data "aws_vpc" "default" { + default = true +} + +data "aws_security_group" "default" { + vpc_id = data.aws_vpc.default.id + name = "default" +} + +data "aws_subnets" "default" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +// Load balancer configuration +resource "aws_lb" "app" { + name = "${var.cluster_name}-alb" + internal = false + load_balancer_type = "application" + security_groups = [data.aws_security_group.default.id] + subnets = data.aws_subnets.default.ids + + enable_deletion_protection = false +} + +resource "aws_lb_target_group" "backend_tg" { + name = "${var.cluster_name}-backend-tg" + port = 4000 + protocol = "HTTP" + vpc_id = data.aws_vpc.default.id + target_type = "ip" + + health_check { + path = "/" # TODO: add a health check endpoint + healthy_threshold = 2 + unhealthy_threshold = 10 + timeout = 60 + interval = 300 + matcher = "200" + } +} + +resource "aws_lb_listener" "app_listener" { + load_balancer_arn = aws_lb.app.arn + port = "80" + protocol = "HTTP" + + default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "Not Found" + status_code = "404" + } + } +} + +resource "aws_lb_listener_rule" "backend" { + listener_arn = aws_lb_listener.app_listener.arn + priority = 200 + + action { + type = "forward" + target_group_arn = aws_lb_target_group.backend_tg.arn + } + + condition { + path_pattern { + values = ["/api/*"] + } + } +} + +// ECS service +resource "aws_ecs_service" "app_service" { + name = "app-service" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.app.arn + desired_count = 1 + launch_type = "FARGATE" + + network_configuration { + subnets = data.aws_subnets.default.ids + assign_public_ip = true + } + + load_balancer { + target_group_arn = aws_lb_target_group.backend_tg.arn + container_name = "backend" + container_port = 4000 + } +} + + +# Get existing IAM info +data "aws_iam_role" "ecs_task_execution_role" { + name = "ecs_task_execution_role" +} + +data "aws_iam_policy" "cloudwatch_logs_policy" { + arn = "arn:aws:iam::${var.aws_account_id}:policy/ECSLogsPolicy" +} + +# Attach the policies to the role +resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { + role = data.aws_iam_role.ecs_task_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} +resource "aws_iam_role_policy_attachment" "cloudwatch_logs_policy_attachment" { + role = data.aws_iam_role.ecs_task_execution_role.name + policy_arn = data.aws_iam_policy.cloudwatch_logs_policy.arn +} + +// ALB IP routing +resource "aws_route53_zone" "main" { + name = var.hosted_zone_name +} + +# resource "aws_route53_record" "backend" { +# zone_id = aws_route53_zone.main.zone_id +# name = "api.${var.hosted_zone_name}" +# type = "A" +# ttl = "300" +# records = [aws_eip.nlb_eip_1.public_ip, aws_eip.nlb_eip_2.public_ip] +# } + +# resource "aws_route53_record" "main" { +# zone_id = aws_route53_zone.main.zone_id +# name = "hackboilerplate.com" +# type = "A" + +# alias { +# name = aws_lb.app.dns_name +# zone_id = aws_lb.app.zone_id +# evaluate_target_health = true +# } +# } diff --git a/infrastructure/backend/outputs.tf b/infrastructure/backend/outputs.tf new file mode 100644 index 00000000..a49e9cae --- /dev/null +++ b/infrastructure/backend/outputs.tf @@ -0,0 +1,4 @@ +output "alb_dns_name" { + description = "The DNS name of the load balancer" + value = aws_lb.app.dns_name +} diff --git a/infrastructure/backend/variables.tf b/infrastructure/backend/variables.tf new file mode 100644 index 00000000..97155da7 --- /dev/null +++ b/infrastructure/backend/variables.tf @@ -0,0 +1,53 @@ +variable "github_repo_owner" { + default = "hack4impact-upenn" + type = string + description = "Name of the GH repo owner, used the pull the docker images" +} + +variable "github_repo_name" { + default = "boilerplate-s2022" + type = string + description = "Name of the GH repo, used to pull the docker images" +} + +variable "hosted_zone_name" { + default = "hackboilerplate.com" + type = string + description = "Domain name" +} + +variable "region" { + default = "us-east-1" + type = string + description = "Launch region for the ECS cluster" +} + +variable "cluster_name" { + default = "app-cluster" + type = string + description = "Name of the ECS cluster" +} + +/// +// ENV VARIABLES +// These are set in .auto.tfvars +/// +variable "aws_account_id" { + type = string +} + +variable "atlas_uri" { + type = string +} + +variable "cookie_secret" { + type = string +} + +variable "sendgrid_api_key" { + type = string +} + +variable "sendgrid_email_address" { + type = string +} From 9f58854d94c19dd6a364b57e9bcc807078fbc892 Mon Sep 17 00:00:00 2001 From: BEW111 Date: Sun, 18 Aug 2024 13:58:18 -0400 Subject: [PATCH 2/2] rebase --- infrastructure/backend/create_client_env.sh | 3 +++ infrastructure/client/main.tf | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) create mode 100755 infrastructure/backend/create_client_env.sh diff --git a/infrastructure/backend/create_client_env.sh b/infrastructure/backend/create_client_env.sh new file mode 100755 index 00000000..06733cc9 --- /dev/null +++ b/infrastructure/backend/create_client_env.sh @@ -0,0 +1,3 @@ +ENV_FILE="../../client/.env" +ALB_DNS=$(terraform output -raw alb_dns_name) +echo "REACT_APP_BACKEND_URL=http://$ALB_DNS" > $ENV_FILE \ No newline at end of file diff --git a/infrastructure/client/main.tf b/infrastructure/client/main.tf index fa000942..9b3ae01b 100644 --- a/infrastructure/client/main.tf +++ b/infrastructure/client/main.tf @@ -7,7 +7,7 @@ resource "local_file" "dotenv" { } provider "aws" { - region = "us-west-1" # Set your desired region + region = "us-west-1" # Set your desired region access_key = var.aws_access_key_id secret_key = var.aws_secret_access_key } @@ -121,17 +121,17 @@ resource "aws_cloudfront_distribution" "cdn" { # Handle 403 errors by serving index.html with a 200 status code custom_error_response { - error_code = 403 - response_code = 200 - response_page_path = "/index.html" + error_code = 403 + response_code = 200 + response_page_path = "/index.html" } # Default root object default_root_object = "index.html" - enabled = true - is_ipv6_enabled = true - price_class = "PriceClass_100" + enabled = true + is_ipv6_enabled = true + price_class = "PriceClass_100" restrictions { geo_restriction {