Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deployment/ecs/backend-prod.hcl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bucket = "mpath-terraform-remote-state"
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/vpc/terraform.tfstate"
region = "us-east-1"
encrypt = true
Expand Down
2 changes: 1 addition & 1 deletion deployment/ecs/envs/bo/backend.hcl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bucket = "mpath-terraform-remote-state"
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/ecs/bo/terraform.tfstate"
region = "us-east-1"
encrypt = true
Expand Down
2 changes: 1 addition & 1 deletion deployment/ecs/envs/bo/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ locals {
data "terraform_remote_state" "root" {
backend = "s3"
config = {
bucket = "mpath-terraform-remote-state"
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/vpc/terraform.tfstate"
region = var.aws_region
encrypt = true
Expand Down
39 changes: 28 additions & 11 deletions deployment/ecs/envs/bo/rds.tf
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Password (only used if var.db_password == null)
resource "random_password" "db" {
length = 24
special = true
override_special = "!@#%^*-_=+"
override_special = "!#%^*-_=+"
}

locals {
db_password_final = coalesce(var.db_password, random_password.db.result)
derived_ecs_tasks_sg_name = coalesce(var.ecs_tasks_sg_name, "mpath-${local.env}-ecs-tasks-sg")
}

# DB Subnet Group (use private subnets from main.tf locals)

resource "aws_db_subnet_group" "this" {
name = "${var.db_identifier}-subnets"
subnet_ids = local.private_subnet_ids
Expand All @@ -26,9 +25,7 @@ resource "aws_security_group" "rds_mysql" {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [
module.ecs_service.service_sg_id
]
security_groups = [module.ecs_service.service_sg_id]
}

egress {
Expand All @@ -41,12 +38,29 @@ resource "aws_security_group" "rds_mysql" {
tags = merge(local.tags, { Name = "${var.db_identifier}-sg" })
}

data "terraform_remote_state" "mgt" {
backend = "s3"
config = {
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/ecs/mgt/terraform.tfstate"
region = var.aws_region
encrypt = true
}
}

resource "aws_security_group_rule" "allow_mysql_from_twingate" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_group_id = aws_security_group.rds_mysql.id
source_security_group_id = data.terraform_remote_state.mgt.outputs.twingate_service_sg_id
}

# RDS Instance – MySQL 8, db.t3.micro, gp2, single-AZ
resource "aws_db_instance" "this" {
identifier = var.db_identifier
engine = "mysql"
engine_version = "8.0" # safe major pin
engine_version = "8.0"
instance_class = "db.t3.micro"

storage_type = "gp2"
Expand All @@ -70,14 +84,16 @@ resource "aws_db_instance" "this" {
# Backups & lifecycle
backup_retention_period = 7
deletion_protection = false
skip_final_snapshot = false
skip_final_snapshot = false

apply_immediately = true

tags = merge(local.tags, { Name = var.db_identifier })
}

# Secrets Manager – JSON bundle for ECS injection


# This creates the *secret container*.
resource "aws_secretsmanager_secret" "db" {
name = var.secret_name
description = "mPATH ${upper(local.env)} DB connection"
Expand All @@ -87,13 +103,14 @@ resource "aws_secretsmanager_secret" "db" {

resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id

secret_string = jsonencode({
adapter = "mysql2"
username = var.db_username
password = local.db_password_final
host = aws_db_instance.this.address
port = 3306
database = var.db_name
url = "mysql2://${var.db_username}:${local.db_password_final}@${aws_db_instance.this.address}:3306/${var.db_name}?encoding=utf8mb4&ssl_mode=required"
url = "mysql2://${var.db_username}:${urlencode(local.db_password_final)}@${aws_db_instance.this.address}:3306/${var.db_name}?encoding=utf8mb4&ssl_mode=required"
})
}
12 changes: 6 additions & 6 deletions deployment/ecs/envs/bo/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
aws_region = "us-east-1"
environment = "bo"
microsoft_secret_path = "mpath/bo/microsoft"
twingate_secret_path = "mpath/bo/twingate"

# --- RDS (cheap single-AZ) ---
db_identifier = "mpath-bo-mysql"
db_name = "mpath_prod"
db_username = "mpath_admin"
db_password = null # leave null => auto-generate + store in Secrets Manager
db_allocated_storage = 20 # gp2 minimum
db_password = null
db_allocated_storage = 20
secret_name = "mpath/bo/db"
kms_key_id = null # or "arn:aws:kms:us-east-1:ACCOUNT:key/...."

Expand Down Expand Up @@ -37,15 +36,16 @@ log_retention_days = 30

waf_allowed_countries = ["US"]

# --- Tags (add App/Env for SG auto-discovery) ---

tags = {
App = "mPATH"
Env = "bo"
Owner = "DevOps Team"
Owner = "Microhealth Platform Engineering Team"
CostCenter = "Engineering"
Application = "mpath"
}

db_secret_arn = aws_secretsmanager_secret.db.arn
mpath_exec = false
custom_domain_name = "mpath-ecs-bo.microhealthllc.com"
custom_domain_name = "mpath-ecs-bo.microhealthllc.com"
readonly_root_filesystem = false
2 changes: 2 additions & 0 deletions deployment/ecs/envs/dha/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
app_env.json
app_env*.json
5 changes: 5 additions & 0 deletions deployment/ecs/envs/dha/backend.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/ecs/dha/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
137 changes: 137 additions & 0 deletions deployment/ecs/envs/dha/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {} # init with: terraform init -backend-config=backend.hcl
}

provider "aws" {
region = var.aws_region
}

locals {
app_name = "mpath"
env = var.environment
}


# Pull shared network from the ROOT stack
data "terraform_remote_state" "root" {
backend = "s3"
config = {
bucket = "mpath-prod-terraform-remote-state"
key = "mpath/vpc/terraform.tfstate"
region = var.aws_region
encrypt = true
}
}

locals {
vpc_id = data.terraform_remote_state.root.outputs.vpc_id
private_subnet_ids = data.terraform_remote_state.root.outputs.private_subnet_ids
public_subnet_ids = data.terraform_remote_state.root.outputs.public_subnet_ids

tags = {
Project = local.app_name
Environment = local.env
ManagedBy = "Terraform"
}
}


data "aws_secretsmanager_secret_version" "mpath_cert" {
secret_id = "/mpath/acm/cert-arn"
}

locals {
_raw_cert_string = data.aws_secretsmanager_secret_version.mpath_cert.secret_string
_maybe_json = try(jsondecode(local._raw_cert_string), null)
_json_cert_arn = local._maybe_json == null ? "" : try(local._maybe_json.cert_arn, "")
acm_cert_from_sm = local._json_cert_arn != "" ? local._json_cert_arn : local._raw_cert_string

# Final selection: explicit var wins; else Secrets Manager
selected_acm_cert_arn = var.acm_certificate_arn != "" ? var.acm_certificate_arn : local.acm_cert_from_sm
# Or, if you’re worried about whitespace:
# selected_acm_cert_arn = trimspace(var.acm_certificate_arn) != "" ? var.acm_certificate_arn : local.acm_cert_from_sm
}



module "ecs_service" {
source = "../../modules/ecs"

cluster_name = "${local.app_name}-${local.env}"
service_name = "${local.app_name}-app-${local.env}"
vpc_id = local.vpc_id
subnet_ids = local.private_subnet_ids
db_secret_arn = aws_secretsmanager_secret.db.arn
app_secret_arn = aws_secretsmanager_secret.app.arn
# Container / service
container_image = var.container_image
container_port = var.container_port
desired_count = var.desired_count
cpu = var.cpu
memory = var.memory
health_check_path = var.health_check_path
mpath_exec = var.mpath_exec
readonly_root_filesystem = var.readonly_root_filesystem
custom_domain_name = var.custom_domain_name
environment_variables = {
RAILS_ENV = "production"
RAILS_SERVE_STATIC_FILES = "true"
NODE_ENV = "production"
}

# ALB/TG/listeners inside the module (per-env ALB)
create_alb = true
public_subnet_ids = local.public_subnet_ids
alb_name = "${local.app_name}-${local.env}-alb"
alb_deletion_protection = true
acm_certificate_arn = local.selected_acm_cert_arn
ssl_policy = var.ssl_policy

# Ops & deployments
platform_version = var.platform_version
deployment_maximum_percent = var.deployment_maximum_percent
deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
deployment_circuit_breaker_enabled = var.deployment_circuit_breaker_enabled
deployment_circuit_breaker_rollback = var.deployment_circuit_breaker_rollback
container_insights_enabled = true
log_retention_days = var.log_retention_days
assign_public_ip = false
microsoft_secret_path = var.microsoft_secret_path
tags = local.tags
}


# Discover this env’s ALB (created by module.ecs_service)
data "aws_lb" "dha_alb" {
name = "${local.app_name}-${local.env}-alb"
depends_on = [module.ecs_service]
}

# Associate the root WAF to this ALB
resource "aws_wafv2_web_acl_association" "mpath_web_acl_assoc" {
resource_arn = data.aws_lb.dha_alb.arn
web_acl_arn = data.terraform_remote_state.root.outputs.waf_web_acl_arn
}

resource "random_password" "secret_key_base" {
length = 64
special = false
}

resource "aws_secretsmanager_secret" "app" {
name = "mpath/dha/app"
description = "mPATH dha app env"
}

resource "aws_secretsmanager_secret_version" "app" {
secret_id = aws_secretsmanager_secret.app.id
secret_string = jsonencode({
SECRET_KEY_BASE = random_password.secret_key_base.result
# ...other keys...
})
}


31 changes: 31 additions & 0 deletions deployment/ecs/envs/dha/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
output "rds_endpoint" {
description = "Full RDS endpoint (hostname:port)."
value = aws_db_instance.this.endpoint
}

output "rds_sg_id" {
description = "Security Group ID attached to the RDS instance."
value = aws_security_group.rds_mysql.id
}

output "rds_db_name" {
description = "Name of the database created."
value = var.db_name
}

output "db_secret_arn" {
description = "Secrets Manager secret ARN with DB credentials."
value = aws_secretsmanager_secret.db.arn
}

output "dha_alb_dns_name" {
value = module.ecs_service.alb_dns_name
}

output "dha_target_group_arn" {
value = module.ecs_service.target_group_arn_effective
}

output "dha_ecs_service_sg" {
value = module.ecs_service.security_group_id
}
Loading