Skip to content
Draft
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
19 changes: 13 additions & 6 deletions .github/workflows/deploy-lean.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Deploy lean (AWS ECS Express Mode)

# Lean-deployment pipeline (see infrastructure/README.md):
# build theme assets -> build image -> push to ECR -> register new
# mmgis-admin + mmgis-publish task-definition revisions -> point the
# ECS Express Mode gateway service's primary container at the new
# image and let the managed rollout run.
# admin + publish task-definition revisions (families from vars) ->
# point the ECS Express Mode gateway service's primary container at
# the new image and let the managed rollout run.
#
# Per the D1 decision, ECS Express Mode owns the ALB, target groups,
# rollout strategy, and scaling — this workflow deliberately defines and
Expand All @@ -15,7 +15,9 @@ name: Deploy lean (AWS ECS Express Mode)
# below still matter (see that step's comment).
#
# Repository configuration:
# vars: AWS_REGION, ECR_REPOSITORY, ECS_CLUSTER, ECS_SERVICE
# vars: AWS_REGION, ECR_REPOSITORY, ECS_CLUSTER, ECS_SERVICE,
# ADMIN_TASK_FAMILY, PUBLISH_TASK_FAMILY (per-environment
# task-def families; default to mmgis-admin / mmgis-publish)
# secrets: AWS_DEPLOY_ROLE_ARN (OIDC-assumable deploy role; no
# long-lived keys)
#
Expand All @@ -39,8 +41,13 @@ permissions:
contents: read

env:
ADMIN_TASK_FAMILY: mmgis-admin
PUBLISH_TASK_FAMILY: mmgis-publish
# Task-definition family names are region-global, so each environment uses
# its own (mmgis-<env>-admin / -publish) to keep a production deploy from
# registering a revision development's publish-by-family flow would pick up.
# These come from Actions variables; the legacy hard-coded names remain as
# documented fallbacks so an unconfigured repo still behaves as before.
ADMIN_TASK_FAMILY: ${{ vars.ADMIN_TASK_FAMILY || 'mmgis-admin' }}
PUBLISH_TASK_FAMILY: ${{ vars.PUBLISH_TASK_FAMILY || 'mmgis-publish' }}

jobs:
deploy:
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ nul

sessions
.terraform/
.terraform.lock.hcl


.mcp.json
Expand Down
415 changes: 284 additions & 131 deletions infrastructure/README.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions infrastructure/terraform/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Local Terraform state, plugins, and crash logs
.terraform/
*.tfstate
*.tfstate.*
crash.log
crash.*.log

# Uncommitted, account-specific inputs (only the *.example files are committed)
terraform.tfvars
*.auto.tfvars
backend.hcl

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copy to backend.hcl and fill in, then: terraform init -backend-config=backend.hcl
# The bucket is bootstrapped once, out-of-band (see infrastructure/README.md).
# S3 native locking (use_lockfile, set in backend.tf) needs no DynamoDB table.
bucket = "mmgis-development-tfstate-<ACCOUNT_ID>"
region = "us-west-2"
13 changes: 13 additions & 0 deletions infrastructure/terraform/environments/development/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
# Development keeps its Terraform state in its OWN dedicated bucket; applying
# one environment can never touch another's state. The bucket is bootstrapped
# once, out-of-band (see infrastructure/README.md) — this config never creates
# it. Bucket + region are supplied at init time via -backend-config so no
# account-specific value is committed:
# terraform init -backend-config=backend.hcl
backend "s3" {
key = "mmgis/development/terraform.tfstate"
encrypt = true
use_lockfile = true
}
}
31 changes: 31 additions & 0 deletions infrastructure/terraform/environments/development/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module "mmgis" {
source = "../../modules/mmgis-environment"

environment = "development"
region = var.region

# Network (uncommitted).
vpc_id = var.vpc_id
private_subnet_ids = var.private_subnet_ids

# RDS — development is disposable, so allow a clean teardown.
db_instance_class = "db.t3.micro"
db_allocated_storage = 20
db_engine_version = "17"
db_multi_az = false
db_skip_final_snapshot = true
rds_ca_bundle_base64 = var.rds_ca_bundle_base64

# CI deploy role: development branch, this repo.
github_repo = "NASA-IMPACT/MMGIS"
deploy_role_branch = "development"

# Development is disposable: no recovery window, so destroy/re-apply
# doesn't collide with secret names held for 30 days.
secret_recovery_window_days = 0

# CloudFront two-phase inputs (empty on the first apply).
express_internal_alb_arn = var.express_internal_alb_arn
express_onaws_endpoint = var.express_onaws_endpoint
express_alb_security_group_id = var.express_alb_security_group_id
}
45 changes: 45 additions & 0 deletions infrastructure/terraform/environments/development/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
output "workflow_variables" {
description = "Values to set as the environment's GitHub Actions variables/secrets."
value = {
AWS_REGION = module.mmgis.aws_region
ECR_REPOSITORY = module.mmgis.ecr_repository_name
ECS_CLUSTER = module.mmgis.ecs_cluster_name
ECS_SERVICE = module.mmgis.ecs_service_name
ADMIN_TASK_FAMILY = module.mmgis.admin_task_family
PUBLISH_TASK_FAMILY = module.mmgis.publish_task_family
AWS_DEPLOY_ROLE_ARN = module.mmgis.deploy_role_arn
}
}

output "express_ingress_paths" {
description = "Read the on.aws endpoint here for the phase-2 CloudFront apply."
value = module.mmgis.express_ingress_paths
}

output "express_service_arn" {
value = module.mmgis.express_service_arn
}

output "rds_endpoint" {
value = module.mmgis.rds_endpoint
}

output "rds_managed_master_secret_arn" {
value = module.mmgis.rds_managed_master_secret_arn
}

output "app_db_secret_arn" {
value = module.mmgis.app_db_secret_arn
}

output "asset_bucket_name" {
value = module.mmgis.asset_bucket_name
}

output "admin_distribution_id" {
value = module.mmgis.admin_distribution_id
}

output "admin_url" {
value = module.mmgis.admin_url
}
11 changes: 11 additions & 0 deletions infrastructure/terraform/environments/development/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
provider "aws" {
region = var.region

default_tags {
tags = {
Project = "MMGIS"
Environment = "development"
ManagedBy = "Terraform"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copy to terraform.tfvars and fill in. terraform.tfvars is git-ignored — it
# carries account-specific network ids that must never be committed.

# AWS region (optional; defaults to us-west-2).
# region = "us-west-2"

# Existing VPC and >= 2 private subnets (different AZs) with NAT egress.
vpc_id = "vpc-xxxxxxxxxxxxxxxxx"
private_subnet_ids = ["subnet-xxxxxxxxxxxxxxxxx", "subnet-yyyyyyyyyyyyyyyyy"]

# base64 of the region's RDS CA bundle. Generate with:
# curl -s https://truststore.pki.rds.amazonaws.com/us-west-2/us-west-2-bundle.pem | base64 | tr -d '\n'
rds_ca_bundle_base64 = "PASTE_BASE64_HERE"

# ── Phase 2 only (leave empty/omit for the first apply) ──
# After phase 1, read these from `aws ecs describe-express-gateway-service`
# (or `terraform output express_ingress_paths`) and re-apply to build CloudFront.
# express_internal_alb_arn = "arn:aws:elasticloadbalancing:us-west-2:ACCOUNT:loadbalancer/app/ecs-express-gateway-alb-xxxx/xxxx"
# express_onaws_endpoint = "mm-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ecs.us-west-2.on.aws"

# Phase 2 (see README): SG id of the ECS-managed ALB
# express_alb_security_group_id = "sg-xxxxxxxxxxxxxxxxx"
43 changes: 43 additions & 0 deletions infrastructure/terraform/environments/development/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Only values that must NOT be committed (network ids, the two-phase CloudFront
# inputs, the CA bundle) are variables. Everything safe to commit is set inline
# in the module call (main.tf). Supply these via an uncommitted terraform.tfvars
# (see terraform.tfvars.example).

variable "region" {
description = "AWS region. Safe to commit as a default; override in tfvars if needed."
type = string
default = "us-west-2"
}

variable "vpc_id" {
description = "Existing VPC id for the development environment."
type = string
}

variable "private_subnet_ids" {
description = "Private subnet ids (>= 2, different AZs) for the Express service and publish task."
type = list(string)
}

variable "rds_ca_bundle_base64" {
description = "base64 of the region's RDS CA bundle (public, region-specific)."
type = string
}

variable "express_internal_alb_arn" {
description = "Phase 2 only. Internal ALB ARN from `aws ecs describe-express-gateway-service`. Leave empty for the first apply."
type = string
default = ""
}

variable "express_onaws_endpoint" {
description = "Phase 2 only. The on.aws endpoint host from the Express service. Leave empty for the first apply."
type = string
default = ""
}

variable "express_alb_security_group_id" {
description = "Phase 2. SG id of the ECS-managed ALB (same describe call as the ALB ARN). Empty in phase 1."
type = string
default = ""
}
10 changes: 10 additions & 0 deletions infrastructure/terraform/environments/development/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.11.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.22.0"
}
}
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy to backend.hcl and fill in, then: terraform init -backend-config=backend.hcl
# Production's state bucket is bootstrapped in #195.
bucket = "mmgis-production-tfstate-<ACCOUNT_ID>"
region = "us-west-2"
12 changes: 12 additions & 0 deletions infrastructure/terraform/environments/production/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
terraform {
# Production keeps its Terraform state in its OWN dedicated bucket. Its
# bootstrap belongs to #195 (this issue creates nothing production-flavored in
# the account); the config exists here so #195 can instantiate it. Bucket +
# region are supplied at init time via -backend-config:
# terraform init -backend-config=backend.hcl
backend "s3" {
key = "mmgis/production/terraform.tfstate"
encrypt = true
use_lockfile = true
}
}
33 changes: 33 additions & 0 deletions infrastructure/terraform/environments/production/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module "mmgis" {
source = "../../modules/mmgis-environment"

environment = "production"
region = var.region

# Network (uncommitted).
vpc_id = var.vpc_id
private_subnet_ids = var.private_subnet_ids

# RDS — runtime spec is IDENTICAL to development (the admin is internal
# tooling; the shipped product is the published dashboards, which serve
# independently of this stack). The environments differ only in DELETION
# POLICY: production always leaves a final snapshot on destroy and keeps
# the default 30-day secret recovery window, while development skips both
# so destroy/rebuild cycles leave no residue.
db_instance_class = "db.t3.micro"
db_allocated_storage = 20
db_engine_version = "17"
db_multi_az = false
db_skip_final_snapshot = false
rds_ca_bundle_base64 = var.rds_ca_bundle_base64

# CI deploy role. NOTE: branch-scoped for now; #195 tightens both environments
# to GitHub-Environment-scoped trust when it wires `environment:` into the job.
github_repo = "NASA-IMPACT/MMGIS"
deploy_role_branch = "production"

# CloudFront two-phase inputs (empty on the first apply).
express_internal_alb_arn = var.express_internal_alb_arn
express_onaws_endpoint = var.express_onaws_endpoint
express_alb_security_group_id = var.express_alb_security_group_id
}
45 changes: 45 additions & 0 deletions infrastructure/terraform/environments/production/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
output "workflow_variables" {
description = "Values to set as the environment's GitHub Actions variables/secrets."
value = {
AWS_REGION = module.mmgis.aws_region
ECR_REPOSITORY = module.mmgis.ecr_repository_name
ECS_CLUSTER = module.mmgis.ecs_cluster_name
ECS_SERVICE = module.mmgis.ecs_service_name
ADMIN_TASK_FAMILY = module.mmgis.admin_task_family
PUBLISH_TASK_FAMILY = module.mmgis.publish_task_family
AWS_DEPLOY_ROLE_ARN = module.mmgis.deploy_role_arn
}
}

output "express_ingress_paths" {
description = "Read the on.aws endpoint here for the phase-2 CloudFront apply."
value = module.mmgis.express_ingress_paths
}

output "express_service_arn" {
value = module.mmgis.express_service_arn
}

output "rds_endpoint" {
value = module.mmgis.rds_endpoint
}

output "rds_managed_master_secret_arn" {
value = module.mmgis.rds_managed_master_secret_arn
}

output "app_db_secret_arn" {
value = module.mmgis.app_db_secret_arn
}

output "asset_bucket_name" {
value = module.mmgis.asset_bucket_name
}

output "admin_distribution_id" {
value = module.mmgis.admin_distribution_id
}

output "admin_url" {
value = module.mmgis.admin_url
}
Loading