video-accessibility/infra/cloud-cdn/main.tf
2025-08-24 16:28:33 -05:00

213 lines
No EOL
5.3 KiB
HCL

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}"
}