Skip to content

Commit

Permalink
Merge pull request #33 from hack4impact-upenn/devops/backend-script
Browse files Browse the repository at this point in the history
Devops/backend script
  • Loading branch information
BEW111 authored Aug 18, 2024
2 parents 749347a + 9f58854 commit 2439842
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 7 deletions.
25 changes: 25 additions & 0 deletions infrastructure/backend/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions infrastructure/backend/create_client_env.sh
Original file line number Diff line number Diff line change
@@ -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
218 changes: 218 additions & 0 deletions infrastructure/backend/main.tf
Original file line number Diff line number Diff line change
@@ -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
# }
# }
4 changes: 4 additions & 0 deletions infrastructure/backend/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "alb_dns_name" {
description = "The DNS name of the load balancer"
value = aws_lb.app.dns_name
}
53 changes: 53 additions & 0 deletions infrastructure/backend/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 7 additions & 7 deletions infrastructure/client/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 2439842

Please sign in to comment.