terraform { required_version = ">= 1.0" required_providers { google = { source = "hashicorp/google" version = "~> 5.0" } } } provider "google" { project = var.project_id region = var.region } # Variables variable "project_id" { description = "Google Cloud Project ID" type = string } variable "region" { description = "Google Cloud Region" type = string default = "us-central1" } variable "domain_name" { description = "Custom domain for the application" type = string default = "" } variable "environment" { description = "Environment (dev, staging, prod)" type = string default = "prod" } # Enable required APIs resource "google_project_service" "required_apis" { for_each = toset([ "compute.googleapis.com", "storage.googleapis.com", "dns.googleapis.com", "certificatemanager.googleapis.com" ]) service = each.value disable_on_destroy = false } # GCS bucket for hosting the SPA resource "google_storage_bucket" "spa_hosting" { name = "accessible-video-spa-${var.project_id}" location = "US" # Multi-regional for global CDN storage_class = "STANDARD" uniform_bucket_level_access = true website { main_page_suffix = "index.html" not_found_page = "index.html" # SPA routing fallback } cors { origin = ["*"] method = ["GET", "HEAD", "OPTIONS"] response_header = ["Content-Type", "Cache-Control"] max_age_seconds = 3600 } # Cache control for static assets lifecycle_rule { condition { age = 365 } action { type = "Delete" } } } # Make bucket public for website hosting resource "google_storage_bucket_iam_member" "spa_public_access" { bucket = google_storage_bucket.spa_hosting.name role = "roles/storage.objectViewer" member = "allUsers" } # Reserve external IP address resource "google_compute_global_address" "spa_ip" { name = "accessible-video-spa-ip" } # Backend service pointing to the storage bucket resource "google_compute_backend_bucket" "spa_backend" { name = "accessible-video-spa-backend" bucket_name = google_storage_bucket.spa_hosting.name enable_cdn = true cdn_policy { cache_mode = "CACHE_ALL_STATIC" default_ttl = 3600 max_ttl = 86400 client_ttl = 7200 negative_caching = true cache_key_policy { include_host = true include_protocol = true include_query_string = false } } } # URL map for routing resource "google_compute_url_map" "spa_url_map" { name = "accessible-video-spa-url-map" default_service = google_compute_backend_bucket.spa_backend.id # API requests go to Cloud Run host_rule { hosts = [var.domain_name != "" ? var.domain_name : "*.googleapis.com"] path_matcher = "api-matcher" } path_matcher { name = "api-matcher" default_service = google_compute_backend_bucket.spa_backend.id path_rule { paths = ["/api/*"] service = "https://accessible-video-api-${random_id.suffix.hex}-uc.a.run.app" } } } # SSL certificate (managed) resource "google_compute_managed_ssl_certificate" "spa_cert" { count = var.domain_name != "" ? 1 : 0 name = "accessible-video-spa-cert" managed { domains = [var.domain_name] } } # HTTPS proxy resource "google_compute_target_https_proxy" "spa_https_proxy" { name = "accessible-video-spa-https-proxy" url_map = google_compute_url_map.spa_url_map.id ssl_certificates = var.domain_name != "" ? [google_compute_managed_ssl_certificate.spa_cert[0].id] : [] } # HTTP proxy for redirect to HTTPS resource "google_compute_target_http_proxy" "spa_http_proxy" { name = "accessible-video-spa-http-proxy" url_map = google_compute_url_map.spa_redirect.id } # URL map for HTTP to HTTPS redirect resource "google_compute_url_map" "spa_redirect" { name = "accessible-video-spa-redirect" default_url_redirect { https_redirect = true strip_query = false } } # Global forwarding rules resource "google_compute_global_forwarding_rule" "spa_https" { name = "accessible-video-spa-https" target = google_compute_target_https_proxy.spa_https_proxy.id port_range = "443" ip_address = google_compute_global_address.spa_ip.address } resource "google_compute_global_forwarding_rule" "spa_http" { name = "accessible-video-spa-http" target = google_compute_target_http_proxy.spa_http_proxy.id port_range = "80" ip_address = google_compute_global_address.spa_ip.address } # Random suffix for unique naming resource "random_id" "suffix" { byte_length = 8 } # Outputs output "spa_bucket_name" { description = "Name of the GCS bucket hosting the SPA" value = google_storage_bucket.spa_hosting.name } output "spa_url" { description = "URL of the deployed SPA" value = var.domain_name != "" ? "https://${var.domain_name}" : "https://${google_compute_global_address.spa_ip.address}" } output "spa_ip_address" { description = "External IP address for the SPA" value = google_compute_global_address.spa_ip.address } output "spa_bucket_url" { description = "Direct GCS bucket URL" value = "https://storage.googleapis.com/${google_storage_bucket.spa_hosting.name}" }