From 138d24c67d2be0af198fe1b1391b5f81e9c5be1f Mon Sep 17 00:00:00 2001 From: BEW111 Date: Sun, 18 Aug 2024 01:52:33 -0400 Subject: [PATCH] switch to alb; bring back frontend --- README.md | 2 +- client/.eslintrc.js | 11 +- client/src/util/api.tsx | 11 +- client/src/util/redux/hooks.ts | 2 +- infrastructure/client/main.tf | 0 infrastructure/main.tf | 240 +++++++++++++++--- infrastructure/modules (unused)/roles/main.tf | 48 ++++ .../service_discovery/main.tf | 24 ++ infrastructure/outputs.tf | 6 +- terraform.tfstate | 9 + 10 files changed, 304 insertions(+), 49 deletions(-) create mode 100644 infrastructure/client/main.tf create mode 100644 infrastructure/modules (unused)/roles/main.tf create mode 100644 infrastructure/modules (unused)/service_discovery/main.tf create mode 100644 terraform.tfstate diff --git a/README.md b/README.md index bc14ef01e..fecd4f513 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The boilerplate uses [SendGrid](https://sendgrid.com) to send emails to users in - Create a SendGrid Account - Register a [Sender Identity](https://docs.sendgrid.com/for-developers/sending-email/sender-identity) (Single Sender recommended for most) -- Create an [API Key](https://docs.sendgrid.com/ui/account-and-settings/api-keys#creating-an-api-key +- Create an [API Key](https://docs.sendgrid.com/ui/account-and-settings/api-keys#creating-an-api-key) ### Environment Variables diff --git a/client/.eslintrc.js b/client/.eslintrc.js index cc6c7dbbb..d0f01bd8a 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -7,7 +7,16 @@ module.exports = { '@typescript-eslint/no-unused-vars': ['warn'], 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 'prettier/prettier': ['error', { endOfLine: 'auto' }], - 'import/extensions': [{ tsx: 'always' }, { ts: 'always' }], + 'import/extensions': [ + 2, + 'always', + { + js: 'never', + jsx: 'never', + ts: 'always', + tsx: 'always', + }, + ], }, env: { browser: true, diff --git a/client/src/util/api.tsx b/client/src/util/api.tsx index 2e127d34d..6e87b68ad 100644 --- a/client/src/util/api.tsx +++ b/client/src/util/api.tsx @@ -41,11 +41,16 @@ async function resolve(promise: Promise) { /** * To UPDATE DURING DEPLOYMENT USING ENVIRONMENT VARIABLES */ -const BACKENDURL = process.env.PUBLIC_URL - ? process.env.PUBLIC_URL +const BACKEND_URL = process.env.BACKEND_URL + ? process.env.BACKEND_URL : 'http://localhost:4000'; -const URLPREFIX = `${BACKENDURL}/api`; +console.log(process.env.BACKEND_URL); +console.log(BACKEND_URL); + +// const BACKEND_URL = 'api.hackboilerplate.com'; + +const URLPREFIX = `${BACKEND_URL}/api`; /** * A function which makes a GET request to the server when given a url and returns the response data after it is resolved by the {@link resolve} function. diff --git a/client/src/util/redux/hooks.ts b/client/src/util/redux/hooks.ts index 293942233..9de8ade58 100644 --- a/client/src/util/redux/hooks.ts +++ b/client/src/util/redux/hooks.ts @@ -1,6 +1,6 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { AppDispatch, RootState } from './store'; +import { AppDispatch, RootState } from './store.ts'; /** * A hook that returns the redux store's dispatch function. This is used to dispatch actions to the redux store. This is a typed version of the `useDispatch` hook from react-redux. diff --git a/infrastructure/client/main.tf b/infrastructure/client/main.tf new file mode 100644 index 000000000..e69de29bb diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 777987ac6..54ecd756a 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -25,6 +25,8 @@ resource "aws_ecs_cluster" "cluster" { } } +# 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 @@ -34,10 +36,39 @@ resource "aws_ecs_task_definition" "app" { family = "app-family" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] - cpu = "256" - memory = "1024" + cpu = "512" + memory = "2048" execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn container_definitions = jsonencode([ + { + name = "frontend" + image = local.client_image_tag + cpu = 256 + memory = 1024 + essential = true + portMappings = [ + { + containerPort = 3000 + hostPort = 3000 + protocol = "tcp" + } + ], + environment = [ + { + name = "REACT_APP_API_URL" + value = "http://${aws_lb.app.dns_name}/api" + } + ], + // TODO: add env vars + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = "/ecs/app-family" + awslogs-region = var.region + awslogs-stream-prefix = "frontend" + } + } + }, { name = "backend" image = local.server_image_tag @@ -70,10 +101,16 @@ resource "aws_ecs_task_definition" "app" { } // 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" @@ -81,67 +118,172 @@ data "aws_subnets" "default" { } } -// Elastic IPs for NLB -resource "aws_eip" "nlb_eip_1" { - vpc = true -} +resource "aws_acm_certificate" "cert" { + domain_name = "hackboilerplate.com" + validation_method = "DNS" -resource "aws_eip" "nlb_eip_2" { - vpc = true + lifecycle { + create_before_destroy = true + } } // Load balancer configuration -resource "aws_lb" "nlb" { - name = "${var.cluster_name}-nlb" +resource "aws_lb" "app" { + name = "${var.cluster_name}-alb" internal = false - load_balancer_type = "network" + load_balancer_type = "application" + security_groups = [data.aws_security_group.default.id] + subnets = data.aws_subnets.default.ids - subnet_mapping { - subnet_id = data.aws_subnets.default.ids[0] - allocation_id = aws_eip.nlb_eip_1.id - } + enable_deletion_protection = false +} - subnet_mapping { - subnet_id = data.aws_subnets.default.ids[1] - allocation_id = aws_eip.nlb_eip_2.id - } +resource "aws_lb_target_group" "frontend_tg" { + name = "${var.cluster_name}-frontend-tg" + port = 3000 + protocol = "HTTP" + vpc_id = data.aws_vpc.default.id + target_type = "ip" - enable_deletion_protection = false + health_check { + path = "/" + healthy_threshold = 2 + unhealthy_threshold = 10 + timeout = 60 + interval = 300 + matcher = "200,301,302" + } } -resource "aws_lb_target_group" "app_tg" { - name = "${var.cluster_name}-tg" +resource "aws_lb_target_group" "backend_tg" { + name = "${var.cluster_name}-backend-tg" port = 4000 - protocol = "TCP" + protocol = "HTTP" vpc_id = data.aws_vpc.default.id target_type = "ip" health_check { - interval = 30 - protocol = "TCP" - healthy_threshold = 3 - unhealthy_threshold = 3 - timeout = 10 + 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_https" { +# load_balancer_arn = aws_lb.app.arn +# port = "443" +# protocol = "HTTPS" +# ssl_policy = "ELBSecurityPolicy-2016-08" +# certificate_arn = aws_acm_certificate.cert.arn + +# default_action { +# type = "fixed-response" +# fixed_response { +# content_type = "text/plain" +# message_body = "Not Found" +# status_code = "404" +# } +# } +# } + + resource "aws_lb_listener" "app_listener" { - load_balancer_arn = aws_lb.nlb.arn - port = 80 - protocol = "TCP" + load_balancer_arn = aws_lb.app.arn + port = "80" + protocol = "HTTP" + + # default_action { + # type = "redirect" + # redirect { + # port = "443" + # protocol = "HTTPS" + # status_code = "HTTP_301" + # } + # } default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "Not Found" + status_code = "404" + } + } +} + +resource "aws_lb_listener_rule" "frontend" { + listener_arn = aws_lb_listener.app_listener.arn + priority = 100 + + action { type = "forward" - target_group_arn = aws_lb_target_group.app_tg.arn + target_group_arn = aws_lb_target_group.frontend_tg.arn + } + + condition { + host_header { + values = ["hackboilerplate.com"] + } } } +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/*"] + } + } +} + +# resource "aws_lb_listener_rule" "frontend_https" { +# listener_arn = aws_lb_listener.app_listener_https.arn +# priority = 100 + +# action { +# type = "forward" +# target_group_arn = aws_lb_target_group.frontend_tg.arn +# } + +# condition { +# host_header { +# values = ["hackboilerplate.com"] +# } +# } +# } + +# resource "aws_lb_listener_rule" "backend_https" { +# listener_arn = aws_lb_listener.app_listener_https.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 # Example count + desired_count = 1 launch_type = "FARGATE" network_configuration { @@ -150,7 +292,13 @@ resource "aws_ecs_service" "app_service" { } load_balancer { - target_group_arn = aws_lb_target_group.app_tg.arn + target_group_arn = aws_lb_target_group.frontend_tg.arn + container_name = "frontend" + container_port = 3000 + } + + load_balancer { + target_group_arn = aws_lb_target_group.backend_tg.arn container_name = "backend" container_port = 4000 } @@ -176,21 +324,29 @@ resource "aws_iam_role_policy_attachment" "cloudwatch_logs_policy_attachment" { policy_arn = data.aws_iam_policy.cloudwatch_logs_policy.arn } -// NLB IP routing +// ALB IP routing resource "aws_route53_zone" "main" { name = var.hosted_zone_name } -resource "aws_route53_record" "backend" { +# 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 = "api.${var.hosted_zone_name}" + name = "hackboilerplate.com" type = "A" - ttl = "300" - records = [aws_eip.nlb_eip_1.public_ip, aws_eip.nlb_eip_2.public_ip] -} -data "aws_route53_zone" "main" { - name = var.hosted_zone_name + alias { + name = aws_lb.app.dns_name + zone_id = aws_lb.app.zone_id + evaluate_target_health = true + } } # So that the ECS role can execute tasks diff --git a/infrastructure/modules (unused)/roles/main.tf b/infrastructure/modules (unused)/roles/main.tf new file mode 100644 index 000000000..2ed4f5d1e --- /dev/null +++ b/infrastructure/modules (unused)/roles/main.tf @@ -0,0 +1,48 @@ +# So that the ECS role can execute tasks +# For CREATING a role +# resource "aws_iam_role" "ecs_task_execution_role" { +# name = "ecs_task_execution_role" + +# assume_role_policy = jsonencode({ +# Version = "2012-10-17" +# Statement = [ +# { +# Action = "sts:AssumeRole" +# Effect = "Allow" +# Principal = { +# Service = "ecs-tasks.amazonaws.com" +# } +# }, +# ] +# }) +# } + +# For CREATING a policy +# resource "aws_iam_policy" "cloudwatch_logs_policy" { +# name = "ECSLogsPolicy" +# description = "Allow ECS Task Execution Role to push logs to CloudWatch" + +# policy = jsonencode({ +# Version = "2012-10-17", +# Statement = [ +# { +# Effect = "Allow", +# Action = [ +# "logs:CreateLogStream", +# "logs:CreateLogGroup" +# ], +# Resource = "arn:aws:logs:*:*:*" +# }, +# { +# Effect = "Allow", +# Action = [ +# "logs:PutLogEvents" +# ], +# Resource = [ +# "arn:aws:logs:*:*:log-group:/ecs/*:log-stream:*", +# "arn:aws:logs:*:*:log-group:/ecs/*" +# ] +# } +# ] +# }) +# } diff --git a/infrastructure/modules (unused)/service_discovery/main.tf b/infrastructure/modules (unused)/service_discovery/main.tf new file mode 100644 index 000000000..2b11946d2 --- /dev/null +++ b/infrastructure/modules (unused)/service_discovery/main.tf @@ -0,0 +1,24 @@ +resource "aws_service_discovery_public_dns_namespace" "example" { + name = "example.hack4impact.org" + description = "example" +} + +resource "aws_service_discovery_service" "example" { + name = "example" + + dns_config { + namespace_id = aws_service_discovery_public_dns_namespace.example.id + + dns_records { + ttl = 10 + type = "A" + } + } + + health_check_config { + failure_threshold = 10 + resource_path = "path" + type = "HTTP" + } +} + diff --git a/infrastructure/outputs.tf b/infrastructure/outputs.tf index df4090b61..a6a776510 100644 --- a/infrastructure/outputs.tf +++ b/infrastructure/outputs.tf @@ -1,4 +1,8 @@ output "route53_nameservers" { - value = data.aws_route53_zone.main.name_servers + value = aws_route53_zone.main.name_servers description = "The nameservers for the Route 53 hosted zone" } + +output "certificate_tags" { + value = aws_acm_certificate.cert.tags_all +} diff --git a/terraform.tfstate b/terraform.tfstate new file mode 100644 index 000000000..b9e0fe7fd --- /dev/null +++ b/terraform.tfstate @@ -0,0 +1,9 @@ +{ + "version": 4, + "terraform_version": "1.7.0", + "serial": 1, + "lineage": "cccaf4c0-37be-e3c9-8134-c976f753de54", + "outputs": {}, + "resources": [], + "check_results": null +}