Skip to content
Open
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
219 changes: 219 additions & 0 deletions terraform/cloud_sql.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Cloud SQL Database Configuration
#
# Strategy:
# - Dev: Creates and manages app-dev Cloud SQL instance
# - Preview: Does NOT create database, uses VPC peering to connect to dev database
# - Prod: Creates and manages app-prod Cloud SQL instance (completely isolated from dev/preview)

# Cloud SQL Instance for DEV environment
resource "google_sql_database_instance" "dev" {
count = var.environment == "dev" ? 1 : 0
name = "app-dev"
database_version = "POSTGRES_15"
region = local.region

settings {
tier = "db-custom-2-3840" # Dev: 2 vCPUs, 3.75GB RAM
availability_type = "ZONAL"
deletion_protection_enabled = false # Allow deletion for dev environment

# Storage configuration
disk_type = "PD_SSD"
disk_size = 50 # 50GB storage for dev
disk_autoresize = true # Enable autoresize

# Backup configuration
backup_configuration {
enabled = true
start_time = "03:00"
point_in_time_recovery_enabled = true
transaction_log_retention_days = 7
backup_retention_settings {
retained_backups = 7
retention_unit = "COUNT"
}
}

# IP configuration - Private IP only via VPC peering
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
enable_private_path_for_google_cloud_services = true
}

# Database flags
database_flags {
name = "max_connections"
value = "100" # Appropriate for dev tier
}
}

depends_on = [
google_service_networking_connection.private_vpc_connection,
google_compute_network.main
]

deletion_protection = false
}

# Cloud SQL Database for DEV
resource "google_sql_database" "dev" {
count = var.environment == "dev" ? 1 : 0
name = "app_db"
instance = google_sql_database_instance.dev[0].name
}

# Generate random password for DEV database
resource "random_password" "dev_password" {
count = var.environment == "dev" ? 1 : 0
length = 32
special = true
upper = true
lower = true
numeric = true
}

# Create secret in Secret Manager for DEV database password
resource "google_secret_manager_secret" "database_password_dev" {
count = var.environment == "dev" ? 1 : 0
secret_id = "database-password-dev"
project = local.project_id

replication {
user_managed {
replicas {
location = local.region
}
}
}

labels = merge(local.common_labels, {
environment = "dev"
purpose = "database-password"
})
}

# Store password in Secret Manager
resource "google_secret_manager_secret_version" "database_password_dev" {
count = var.environment == "dev" ? 1 : 0
secret = google_secret_manager_secret.database_password_dev[0].id
secret_data = random_password.dev_password[0].result
}

# Cloud SQL User for DEV
resource "google_sql_user" "dev" {
count = var.environment == "dev" ? 1 : 0
name = "app_user"
instance = google_sql_database_instance.dev[0].name
password = random_password.dev_password[0].result
type = "BUILT_IN"
}


# Cloud SQL Instance for PROD environment (completely isolated)
resource "google_sql_database_instance" "prod" {
count = var.environment == "prod" ? 1 : 0
name = "app-prod"
database_version = "POSTGRES_15"
region = local.region

settings {
tier = "db-custom-4-7680" # Prod: 4 vCPUs, 7.5GB RAM
availability_type = "ZONAL"
deletion_protection_enabled = true # Protect production database

# Storage configuration
disk_type = "PD_SSD"
disk_size = 100 # 100GB storage for prod
disk_autoresize = true # Enable autoresize

# Backup configuration
backup_configuration {
enabled = true
start_time = "03:00"
point_in_time_recovery_enabled = true
transaction_log_retention_days = 7
backup_retention_settings {
retained_backups = 7
retention_unit = "COUNT"
}
}

# IP configuration - Private IP only via VPC peering
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
enable_private_path_for_google_cloud_services = true
}

# Database flags
database_flags {
name = "max_connections"
value = "200" # Higher for production tier
}
}

depends_on = [
google_service_networking_connection.private_vpc_connection,
google_compute_network.main
]

deletion_protection = true # Critical: Protect production database
}

# Cloud SQL Database for PROD
resource "google_sql_database" "prod" {
count = var.environment == "prod" ? 1 : 0
name = "app_db"
instance = google_sql_database_instance.prod[0].name
}

# Generate random password for PROD database
resource "random_password" "prod_password" {
count = var.environment == "prod" ? 1 : 0
length = 32
special = true
upper = true
lower = true
numeric = true
}

# Create secret in Secret Manager for PROD database password
resource "google_secret_manager_secret" "database_password_prod" {
count = var.environment == "prod" ? 1 : 0
secret_id = "database-password-prod"
project = local.project_id

replication {
user_managed {
replicas {
location = local.region
}
}
}

labels = merge(local.common_labels, {
environment = "prod"
purpose = "database-password"
})
}

# Store password in Secret Manager
resource "google_secret_manager_secret_version" "database_password_prod" {
count = var.environment == "prod" ? 1 : 0
secret = google_secret_manager_secret.database_password_prod[0].id
secret_data = random_password.prod_password[0].result
}

# Cloud SQL User for PROD
resource "google_sql_user" "prod" {
count = var.environment == "prod" ? 1 : 0
name = "app_user"
instance = google_sql_database_instance.prod[0].name
password = random_password.prod_password[0].result
type = "BUILT_IN"
}

# Note: VPC Peering configuration has been moved to vpc.tf for better organization
# See vpc.tf for preview-to-dev and dev-to-preview peering resources

4 changes: 4 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ terraform {
source = "hashicorp/google-beta"
version = "~> 6.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1"
}
}

# Use existing GCS backend for state storage
Expand Down
16 changes: 16 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,20 @@ variable "allow_public_access" {
description = "DEPRECATED: Use firewall_rules instead. Allow public access (0.0.0.0/0) to application services."
type = bool
default = true
}

# Database passwords are stored in Secret Manager:
# - database-password-dev (for dev environment)
# - database-password-prod (for prod environment)
# No variable needed - passwords are retrieved from Secret Manager

# VPC Peering configuration for preview environments
variable "preview_vpc_names" {
description = "Set of preview VPC network names that should have peering with dev environment. Example: toset([\"labs-asp-vpc-preview-pr-123\", \"labs-asp-vpc-preview-pr-124\"])"
type = set(string)
default = []

# Only used when environment is "dev"
# When a preview environment is created, add its VPC name here and redeploy dev
# Format: "labs-asp-vpc-preview-pr-{number}"
}
61 changes: 61 additions & 0 deletions terraform/vpc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,64 @@ resource "google_compute_firewall" "mastra_app" {
description = "Allow Mastra API access on port ${var.firewall_rules.mastra_api.port} from VPC and approved external IPs"
}

# ============================================================================
# VPC Peering Configuration
# ============================================================================
# Peering between Preview and Dev environments to allow preview to access dev database

# VPC Peering for Preview environments to access Dev database
# This allows preview environments to connect to the dev Cloud SQL instance
resource "google_compute_network_peering" "preview_to_dev" {
count = startswith(var.environment, "preview-") ? 1 : 0
name = "preview-to-dev-peering-${var.environment}"
network = google_compute_network.main.id
peer_network = data.google_compute_network.dev_vpc[0].id

# Allow custom routes to enable connectivity
import_custom_routes = true
export_custom_routes = true

depends_on = [
google_compute_network.main,
google_service_networking_connection.private_vpc_connection
]
}

# Data source to get dev VPC network for peering
data "google_compute_network" "dev_vpc" {
count = startswith(var.environment, "preview-") ? 1 : 0
name = "labs-asp-vpc-dev"
project = local.project_id
}

# Reverse VPC Peering from Dev to Preview environments
# This allows preview environments to connect back to dev VPC
# Note: VPC peering is bidirectional - both sides need to be created
# The preview-to-dev peering is created above when deploying preview environments
# The reverse peering (dev-to-preview) is created here when deploying the dev environment

# Data sources to get preview VPC networks for reverse peering
# These are discovered based on the preview_vpc_names variable
data "google_compute_network" "preview_vpcs" {
for_each = var.environment == "dev" ? var.preview_vpc_names : toset([])
name = each.value
project = local.project_id
}

# Reverse peering from dev to preview environments
# This completes the bidirectional peering connection
resource "google_compute_network_peering" "dev_to_preview" {
for_each = var.environment == "dev" ? var.preview_vpc_names : toset([])
name = "dev-to-preview-peering-${replace(each.value, "labs-asp-vpc-", "")}"
network = google_compute_network.main.id
peer_network = data.google_compute_network.preview_vpcs[each.value].id

import_custom_routes = true
export_custom_routes = true

depends_on = [
google_compute_network.main,
google_service_networking_connection.private_vpc_connection
]
}

Comment on lines +332 to +348
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear why you are using VPC peering at all. Can't the Cloud SQL instance authorize a specific CIDR ranges. Since preview uses 10.1.0.0/16, why not just add that to the Cloud SQL's authorized networks?

With the current setup I think we would have to manage the dev->preview VPC connection as well right?

I might be a bit ignorant here, anything you can share about why bidirectional VPC peering is a good idea might help me understand better.