feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# GCP Provider – Full implementation for Claude Code Agent Monitor
|
||||
#
|
||||
# Architecture:
|
||||
# VPC → Cloud Run (blue/green) → Filestore → Cloud Load Balancer
|
||||
# → Cloud Monitoring → Managed SSL Certificate
|
||||
#
|
||||
# Cloud Run is chosen over GKE for cost efficiency and operational simplicity
|
||||
# for this containerised workload. Filestore provides NFS for SQLite.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
provider "google" {
|
||||
project = var.gcp_project_id
|
||||
region = var.region
|
||||
}
|
||||
|
||||
provider "google-beta" {
|
||||
project = var.gcp_project_id
|
||||
region = var.region
|
||||
}
|
||||
|
||||
# ── Locals ──────────────────────────────────────────────────────────────────
|
||||
|
||||
locals {
|
||||
name_prefix = lower(replace("${var.project_name}-${var.environment}", "_", "-"))
|
||||
|
||||
common_labels = merge(
|
||||
{
|
||||
project = replace(var.project_name, "-", "_")
|
||||
environment = var.environment
|
||||
managed_by = "terraform"
|
||||
cloud_provider = "gcp"
|
||||
},
|
||||
{ for k, v in var.tags : replace(k, "-", "_") => replace(v, "-", "_") },
|
||||
)
|
||||
}
|
||||
|
||||
# ── Enable required APIs ───────────────────────────────────────────────────
|
||||
|
||||
resource "google_project_service" "apis" {
|
||||
for_each = toset([
|
||||
"run.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
"file.googleapis.com",
|
||||
"vpcaccess.googleapis.com",
|
||||
"monitoring.googleapis.com",
|
||||
"logging.googleapis.com",
|
||||
"certificatemanager.googleapis.com",
|
||||
])
|
||||
|
||||
service = each.value
|
||||
disable_on_destroy = false
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# VPC Network
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_network" "main" {
|
||||
name = "${local.name_prefix}-vpc"
|
||||
auto_create_subnetworks = false
|
||||
|
||||
depends_on = [google_project_service.apis]
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "private" {
|
||||
name = "${local.name_prefix}-private"
|
||||
ip_cidr_range = var.private_subnet_cidrs[0]
|
||||
region = var.region
|
||||
network = google_compute_network.main.id
|
||||
|
||||
private_ip_google_access = true
|
||||
|
||||
log_config {
|
||||
aggregation_interval = "INTERVAL_5_SEC"
|
||||
flow_sampling = 0.5
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "proxy" {
|
||||
name = "${local.name_prefix}-proxy"
|
||||
ip_cidr_range = "10.0.100.0/24"
|
||||
region = var.region
|
||||
network = google_compute_network.main.id
|
||||
purpose = "REGIONAL_MANAGED_PROXY"
|
||||
role = "ACTIVE"
|
||||
}
|
||||
|
||||
# Cloud NAT for outbound internet
|
||||
resource "google_compute_router" "main" {
|
||||
name = "${local.name_prefix}-router"
|
||||
region = var.region
|
||||
network = google_compute_network.main.id
|
||||
}
|
||||
|
||||
resource "google_compute_router_nat" "main" {
|
||||
name = "${local.name_prefix}-nat"
|
||||
router = google_compute_router.main.name
|
||||
region = var.region
|
||||
nat_ip_allocate_option = "AUTO_ONLY"
|
||||
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
|
||||
|
||||
log_config {
|
||||
enable = true
|
||||
filter = "ERRORS_ONLY"
|
||||
}
|
||||
}
|
||||
|
||||
# VPC Connector for Cloud Run → Filestore
|
||||
resource "google_vpc_access_connector" "main" {
|
||||
name = "${local.name_prefix}-conn"
|
||||
region = var.region
|
||||
network = google_compute_network.main.id
|
||||
ip_cidr_range = "10.0.200.0/28"
|
||||
min_instances = 2
|
||||
max_instances = var.environment == "production" ? 10 : 3
|
||||
|
||||
depends_on = [google_project_service.apis]
|
||||
}
|
||||
|
||||
# Firewall rules
|
||||
resource "google_compute_firewall" "allow_health_checks" {
|
||||
name = "${local.name_prefix}-allow-health-checks"
|
||||
network = google_compute_network.main.id
|
||||
|
||||
allow {
|
||||
protocol = "tcp"
|
||||
ports = [tostring(var.app_port), tostring(var.mcp_port)]
|
||||
}
|
||||
|
||||
source_ranges = ["130.211.0.0/22", "35.191.0.0/16"] # GCP health check ranges
|
||||
target_tags = ["${local.name_prefix}-app"]
|
||||
}
|
||||
|
||||
resource "google_compute_firewall" "allow_internal" {
|
||||
name = "${local.name_prefix}-allow-internal"
|
||||
network = google_compute_network.main.id
|
||||
|
||||
allow {
|
||||
protocol = "tcp"
|
||||
ports = [tostring(var.app_port), tostring(var.mcp_port), "2049"]
|
||||
}
|
||||
|
||||
source_ranges = [var.vpc_cidr]
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Filestore (NFS for SQLite persistence)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_filestore_instance" "main" {
|
||||
name = "${local.name_prefix}-data"
|
||||
location = "${var.region}-b"
|
||||
tier = var.environment == "production" ? "BASIC_SSD" : "BASIC_HDD"
|
||||
|
||||
file_shares {
|
||||
name = "appdata"
|
||||
capacity_gb = var.storage_size_gb
|
||||
}
|
||||
|
||||
networks {
|
||||
network = google_compute_network.main.name
|
||||
modes = ["MODE_IPV4"]
|
||||
}
|
||||
|
||||
labels = local.common_labels
|
||||
|
||||
depends_on = [google_project_service.apis]
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Cloud Run services (Blue / Green)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_cloud_run_v2_service" "blue" {
|
||||
name = "${local.name_prefix}-blue"
|
||||
location = var.region
|
||||
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
|
||||
|
||||
template {
|
||||
scaling {
|
||||
min_instance_count = var.active_deployment_slot == "blue" ? var.min_replicas : 0
|
||||
max_instance_count = var.active_deployment_slot == "blue" ? var.max_replicas : 1
|
||||
}
|
||||
|
||||
vpc_access {
|
||||
connector = google_vpc_access_connector.main.id
|
||||
egress = "ALL_TRAFFIC"
|
||||
}
|
||||
|
||||
containers {
|
||||
image = var.app_container_image
|
||||
name = "app"
|
||||
|
||||
ports {
|
||||
container_port = var.app_port
|
||||
}
|
||||
|
||||
resources {
|
||||
limits = {
|
||||
cpu = "${var.cpu}m"
|
||||
memory = "${var.memory}Mi"
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "env" {
|
||||
for_each = var.environment_variables
|
||||
content {
|
||||
name = env.key
|
||||
value = env.value
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "FILESTORE_IP"
|
||||
value = google_filestore_instance.main.networks[0].ip_addresses[0]
|
||||
}
|
||||
|
||||
startup_probe {
|
||||
http_get {
|
||||
path = var.health_check_path
|
||||
port = var.app_port
|
||||
}
|
||||
initial_delay_seconds = 10
|
||||
period_seconds = 10
|
||||
failure_threshold = 5
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
http_get {
|
||||
path = var.health_check_path
|
||||
port = var.app_port
|
||||
}
|
||||
period_seconds = 30
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
volume_mounts {
|
||||
name = "app-data"
|
||||
mount_path = "/app/data"
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "containers" {
|
||||
for_each = var.mcp_container_image != "" ? [1] : []
|
||||
content {
|
||||
image = var.mcp_container_image
|
||||
name = "mcp-sidecar"
|
||||
|
||||
ports {
|
||||
container_port = var.mcp_port
|
||||
}
|
||||
|
||||
resources {
|
||||
limits = {
|
||||
cpu = "250m"
|
||||
memory = "256Mi"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "NODE_ENV"
|
||||
value = "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
volumes {
|
||||
name = "app-data"
|
||||
nfs {
|
||||
server = google_filestore_instance.main.networks[0].ip_addresses[0]
|
||||
path = "/appdata"
|
||||
read_only = false
|
||||
}
|
||||
}
|
||||
|
||||
session_affinity = true
|
||||
timeout = "300s"
|
||||
}
|
||||
|
||||
labels = local.common_labels
|
||||
|
||||
depends_on = [google_project_service.apis]
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
client,
|
||||
client_version,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_cloud_run_v2_service" "green" {
|
||||
name = "${local.name_prefix}-green"
|
||||
location = var.region
|
||||
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
|
||||
|
||||
template {
|
||||
scaling {
|
||||
min_instance_count = var.active_deployment_slot == "green" ? var.min_replicas : 0
|
||||
max_instance_count = var.active_deployment_slot == "green" ? var.max_replicas : 1
|
||||
}
|
||||
|
||||
vpc_access {
|
||||
connector = google_vpc_access_connector.main.id
|
||||
egress = "ALL_TRAFFIC"
|
||||
}
|
||||
|
||||
containers {
|
||||
image = var.app_container_image
|
||||
name = "app"
|
||||
|
||||
ports {
|
||||
container_port = var.app_port
|
||||
}
|
||||
|
||||
resources {
|
||||
limits = {
|
||||
cpu = "${var.cpu}m"
|
||||
memory = "${var.memory}Mi"
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "env" {
|
||||
for_each = var.environment_variables
|
||||
content {
|
||||
name = env.key
|
||||
value = env.value
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "FILESTORE_IP"
|
||||
value = google_filestore_instance.main.networks[0].ip_addresses[0]
|
||||
}
|
||||
|
||||
startup_probe {
|
||||
http_get {
|
||||
path = var.health_check_path
|
||||
port = var.app_port
|
||||
}
|
||||
initial_delay_seconds = 10
|
||||
period_seconds = 10
|
||||
failure_threshold = 5
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
http_get {
|
||||
path = var.health_check_path
|
||||
port = var.app_port
|
||||
}
|
||||
period_seconds = 30
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
volume_mounts {
|
||||
name = "app-data"
|
||||
mount_path = "/app/data"
|
||||
}
|
||||
}
|
||||
|
||||
volumes {
|
||||
name = "app-data"
|
||||
nfs {
|
||||
server = google_filestore_instance.main.networks[0].ip_addresses[0]
|
||||
path = "/appdata"
|
||||
read_only = false
|
||||
}
|
||||
}
|
||||
|
||||
session_affinity = true
|
||||
timeout = "300s"
|
||||
}
|
||||
|
||||
labels = local.common_labels
|
||||
|
||||
depends_on = [google_project_service.apis]
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
client,
|
||||
client_version,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# IAM – restrict access to load balancer service account only
|
||||
# Cloud Run ingress is set to INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER,
|
||||
# so public IAM bindings are not needed. The LB routes traffic internally.
|
||||
# To grant specific service account access, replace with:
|
||||
# member = "serviceAccount:<your-lb-service-account>@<project>.iam.gserviceaccount.com"
|
||||
#
|
||||
# resource "google_cloud_run_v2_service_iam_member" "blue_invoker" {
|
||||
# name = google_cloud_run_v2_service.blue.name
|
||||
# location = var.region
|
||||
# role = "roles/run.invoker"
|
||||
# member = "serviceAccount:${var.project_id}-compute@developer.gserviceaccount.com"
|
||||
# }
|
||||
#
|
||||
# resource "google_cloud_run_v2_service_iam_member" "green_invoker" {
|
||||
# name = google_cloud_run_v2_service.green.name
|
||||
# location = var.region
|
||||
# role = "roles/run.invoker"
|
||||
# member = "serviceAccount:${var.project_id}-compute@developer.gserviceaccount.com"
|
||||
# }
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# External Application Load Balancer
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Serverless NEGs for Cloud Run
|
||||
resource "google_compute_region_network_endpoint_group" "blue" {
|
||||
name = "${local.name_prefix}-blue-neg"
|
||||
region = var.region
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.blue.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "green" {
|
||||
name = "${local.name_prefix}-green-neg"
|
||||
region = var.region
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.green.name
|
||||
}
|
||||
}
|
||||
|
||||
# Backend service with weighted backends for blue/green
|
||||
resource "google_compute_backend_service" "main" {
|
||||
name = "${local.name_prefix}-backend"
|
||||
protocol = "HTTP"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
timeout_sec = 300 # WebSocket support
|
||||
|
||||
session_affinity = "GENERATED_COOKIE"
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.blue.id
|
||||
capacity_scaler = var.blue_weight / 100
|
||||
}
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.green.id
|
||||
capacity_scaler = var.green_weight / 100
|
||||
}
|
||||
|
||||
health_checks = [google_compute_health_check.main.id]
|
||||
|
||||
log_config {
|
||||
enable = true
|
||||
sample_rate = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_health_check" "main" {
|
||||
name = "${local.name_prefix}-hc"
|
||||
|
||||
http_health_check {
|
||||
port = var.app_port
|
||||
request_path = var.health_check_path
|
||||
}
|
||||
|
||||
check_interval_sec = var.health_check_interval
|
||||
timeout_sec = var.health_check_timeout
|
||||
healthy_threshold = var.health_check_healthy_threshold
|
||||
unhealthy_threshold = var.health_check_unhealthy_threshold
|
||||
}
|
||||
|
||||
# URL map
|
||||
resource "google_compute_url_map" "main" {
|
||||
name = "${local.name_prefix}-urlmap"
|
||||
default_service = google_compute_backend_service.main.id
|
||||
}
|
||||
|
||||
# Managed SSL certificate (optional)
|
||||
resource "google_compute_managed_ssl_certificate" "main" {
|
||||
count = var.domain_name != "" ? 1 : 0
|
||||
|
||||
name = "${local.name_prefix}-cert"
|
||||
|
||||
managed {
|
||||
domains = [var.domain_name]
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS proxy
|
||||
resource "google_compute_target_https_proxy" "main" {
|
||||
count = var.domain_name != "" ? 1 : 0
|
||||
|
||||
name = "${local.name_prefix}-https-proxy"
|
||||
url_map = google_compute_url_map.main.id
|
||||
ssl_certificates = [google_compute_managed_ssl_certificate.main[0].id]
|
||||
}
|
||||
|
||||
# HTTP proxy (for redirect or direct access)
|
||||
resource "google_compute_target_http_proxy" "main" {
|
||||
name = "${local.name_prefix}-http-proxy"
|
||||
url_map = google_compute_url_map.main.id
|
||||
}
|
||||
|
||||
# Global forwarding rules
|
||||
resource "google_compute_global_forwarding_rule" "https" {
|
||||
count = var.domain_name != "" ? 1 : 0
|
||||
|
||||
name = "${local.name_prefix}-https"
|
||||
target = google_compute_target_https_proxy.main[0].id
|
||||
port_range = "443"
|
||||
ip_protocol = "TCP"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "http" {
|
||||
name = "${local.name_prefix}-http"
|
||||
target = google_compute_target_http_proxy.main.id
|
||||
port_range = "80"
|
||||
ip_protocol = "TCP"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Cloud Monitoring – Alert policies
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_monitoring_notification_channel" "email" {
|
||||
count = var.alert_email != "" ? 1 : 0
|
||||
|
||||
display_name = "${local.name_prefix}-email"
|
||||
type = "email"
|
||||
|
||||
labels = {
|
||||
email_address = var.alert_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_monitoring_alert_policy" "high_latency" {
|
||||
count = var.enable_monitoring ? 1 : 0
|
||||
|
||||
display_name = "${local.name_prefix}-high-latency"
|
||||
combiner = "OR"
|
||||
|
||||
conditions {
|
||||
display_name = "Cloud Run request latency > 2s"
|
||||
|
||||
condition_threshold {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_latencies\""
|
||||
comparison = "COMPARISON_GT"
|
||||
duration = "300s"
|
||||
|
||||
threshold_value = 2000 # ms
|
||||
|
||||
aggregations {
|
||||
alignment_period = "60s"
|
||||
per_series_aligner = "ALIGN_PERCENTILE_99"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification_channels = var.alert_email != "" ? [google_monitoring_notification_channel.email[0].id] : []
|
||||
|
||||
alert_strategy {
|
||||
auto_close = "604800s"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_monitoring_alert_policy" "high_error_rate" {
|
||||
count = var.enable_monitoring ? 1 : 0
|
||||
|
||||
display_name = "${local.name_prefix}-high-error-rate"
|
||||
combiner = "OR"
|
||||
|
||||
conditions {
|
||||
display_name = "Cloud Run 5xx error rate"
|
||||
|
||||
condition_threshold {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_count\" AND metric.labels.response_code_class=\"5xx\""
|
||||
comparison = "COMPARISON_GT"
|
||||
duration = "300s"
|
||||
|
||||
threshold_value = 10
|
||||
|
||||
aggregations {
|
||||
alignment_period = "60s"
|
||||
per_series_aligner = "ALIGN_RATE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification_channels = var.alert_email != "" ? [google_monitoring_notification_channel.email[0].id] : []
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Cloud Monitoring Dashboard
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_monitoring_dashboard" "main" {
|
||||
count = var.enable_monitoring ? 1 : 0
|
||||
|
||||
dashboard_json = jsonencode({
|
||||
displayName = "${local.name_prefix} Dashboard"
|
||||
gridLayout = {
|
||||
columns = 2
|
||||
widgets = [
|
||||
{
|
||||
title = "Request Count"
|
||||
xyChart = {
|
||||
dataSets = [{
|
||||
timeSeriesQuery = {
|
||||
timeSeriesFilter = {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_count\""
|
||||
aggregation = {
|
||||
alignmentPeriod = "60s"
|
||||
perSeriesAligner = "ALIGN_RATE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
title = "Request Latency (p99)"
|
||||
xyChart = {
|
||||
dataSets = [{
|
||||
timeSeriesQuery = {
|
||||
timeSeriesFilter = {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_latencies\""
|
||||
aggregation = {
|
||||
alignmentPeriod = "60s"
|
||||
perSeriesAligner = "ALIGN_PERCENTILE_99"
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
title = "Instance Count"
|
||||
xyChart = {
|
||||
dataSets = [{
|
||||
timeSeriesQuery = {
|
||||
timeSeriesFilter = {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/container/instance_count\""
|
||||
aggregation = {
|
||||
alignmentPeriod = "60s"
|
||||
perSeriesAligner = "ALIGN_MEAN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
title = "CPU Utilization"
|
||||
xyChart = {
|
||||
dataSets = [{
|
||||
timeSeriesQuery = {
|
||||
timeSeriesFilter = {
|
||||
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/container/cpu/utilizations\""
|
||||
aggregation = {
|
||||
alignmentPeriod = "60s"
|
||||
perSeriesAligner = "ALIGN_PERCENTILE_99"
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# GCP provider outputs
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
output "application_url" {
|
||||
description = "Public URL of the Claude Code Agent Monitor dashboard"
|
||||
value = var.domain_name != "" ? "https://${var.domain_name}" : "http://${google_compute_global_forwarding_rule.http.ip_address}"
|
||||
}
|
||||
|
||||
output "load_balancer_ip" {
|
||||
description = "External IP address of the load balancer"
|
||||
value = google_compute_global_forwarding_rule.http.ip_address
|
||||
}
|
||||
|
||||
output "blue_service_url" {
|
||||
description = "URL of the blue Cloud Run service"
|
||||
value = google_cloud_run_v2_service.blue.uri
|
||||
}
|
||||
|
||||
output "green_service_url" {
|
||||
description = "URL of the green Cloud Run service"
|
||||
value = google_cloud_run_v2_service.green.uri
|
||||
}
|
||||
|
||||
output "vpc_id" {
|
||||
description = "Self-link of the VPC network"
|
||||
value = google_compute_network.main.self_link
|
||||
}
|
||||
|
||||
output "filestore_ip" {
|
||||
description = "IP address of the Filestore instance"
|
||||
value = google_filestore_instance.main.networks[0].ip_addresses[0]
|
||||
}
|
||||
|
||||
output "filestore_share" {
|
||||
description = "Filestore share name"
|
||||
value = google_filestore_instance.main.file_shares[0].name
|
||||
}
|
||||
|
||||
output "monitoring_dashboard_url" {
|
||||
description = "Cloud Monitoring dashboard URL"
|
||||
value = var.enable_monitoring ? "https://console.cloud.google.com/monitoring/dashboards?project=${var.gcp_project_id}" : "monitoring disabled"
|
||||
}
|
||||
|
||||
output "project_id" {
|
||||
description = "GCP project ID"
|
||||
value = var.gcp_project_id
|
||||
}
|
||||
|
||||
output "region" {
|
||||
description = "GCP region"
|
||||
value = var.region
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# GCP provider – Terraform and provider constraints
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
google-beta = {
|
||||
source = "hashicorp/google-beta"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# GCP provider variables
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "project_name" {
|
||||
description = "Project identifier used in resource naming"
|
||||
type = string
|
||||
default = "claude-agent-monitor"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Deployment environment: dev, staging, or production"
|
||||
type = string
|
||||
validation {
|
||||
condition = contains(["dev", "staging", "production"], var.environment)
|
||||
error_message = "environment must be one of: dev, staging, production."
|
||||
}
|
||||
}
|
||||
|
||||
variable "gcp_project_id" {
|
||||
description = "GCP project ID for resource deployment"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "GCP region for resource deployment"
|
||||
type = string
|
||||
default = "us-central1"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Additional labels to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
# ── Networking ──────────────────────────────────────────────────────────────
|
||||
|
||||
variable "vpc_cidr" {
|
||||
description = "CIDR block for the VPC (used for firewall rules)"
|
||||
type = string
|
||||
default = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
variable "private_subnet_cidrs" {
|
||||
description = "CIDR blocks for private subnets"
|
||||
type = list(string)
|
||||
default = ["10.0.11.0/24"]
|
||||
}
|
||||
|
||||
# ── Compute ─────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "app_container_image" {
|
||||
description = "Container image URI for the main application"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "mcp_container_image" {
|
||||
description = "Container image URI for the MCP sidecar (empty to disable)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "app_port" {
|
||||
description = "Application container port"
|
||||
type = number
|
||||
default = 4820
|
||||
}
|
||||
|
||||
variable "mcp_port" {
|
||||
description = "MCP sidecar container port"
|
||||
type = number
|
||||
default = 8819
|
||||
}
|
||||
|
||||
variable "cpu" {
|
||||
description = "CPU millicores for each Cloud Run instance"
|
||||
type = number
|
||||
default = 512
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
description = "Memory in MiB for each Cloud Run instance"
|
||||
type = number
|
||||
default = 1024
|
||||
}
|
||||
|
||||
variable "min_replicas" {
|
||||
description = "Minimum number of Cloud Run instances"
|
||||
type = number
|
||||
default = 0
|
||||
}
|
||||
|
||||
variable "max_replicas" {
|
||||
description = "Maximum number of Cloud Run instances"
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
variable "environment_variables" {
|
||||
description = "Environment variables for the application container"
|
||||
type = map(string)
|
||||
default = {
|
||||
NODE_ENV = "production"
|
||||
DASHBOARD_PORT = "4820"
|
||||
}
|
||||
}
|
||||
|
||||
# ── Deployment ──────────────────────────────────────────────────────────────
|
||||
|
||||
variable "active_deployment_slot" {
|
||||
description = "Active deployment slot: blue or green"
|
||||
type = string
|
||||
default = "blue"
|
||||
validation {
|
||||
condition = contains(["blue", "green"], var.active_deployment_slot)
|
||||
error_message = "active_deployment_slot must be blue or green."
|
||||
}
|
||||
}
|
||||
|
||||
variable "blue_weight" {
|
||||
description = "Traffic weight for blue service (0-100)"
|
||||
type = number
|
||||
default = 100
|
||||
validation {
|
||||
condition = var.blue_weight >= 0 && var.blue_weight <= 100
|
||||
error_message = "blue_weight must be between 0 and 100."
|
||||
}
|
||||
}
|
||||
|
||||
variable "green_weight" {
|
||||
description = "Traffic weight for green service (0-100)"
|
||||
type = number
|
||||
default = 0
|
||||
validation {
|
||||
condition = var.green_weight >= 0 && var.green_weight <= 100
|
||||
error_message = "green_weight must be between 0 and 100."
|
||||
}
|
||||
}
|
||||
|
||||
# ── TLS / Domain ────────────────────────────────────────────────────────────
|
||||
|
||||
variable "domain_name" {
|
||||
description = "FQDN for managed SSL certificate (empty for HTTP only)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
# ── Storage ─────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "storage_size_gb" {
|
||||
description = "Filestore capacity in GiB"
|
||||
type = number
|
||||
default = 1024 # Filestore minimum for BASIC_HDD
|
||||
}
|
||||
|
||||
# ── Health check ────────────────────────────────────────────────────────────
|
||||
|
||||
variable "health_check_path" {
|
||||
description = "HTTP path for health checks"
|
||||
type = string
|
||||
default = "/api/health"
|
||||
}
|
||||
|
||||
variable "health_check_interval" {
|
||||
description = "Seconds between health checks"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "health_check_timeout" {
|
||||
description = "Seconds before a health check times out"
|
||||
type = number
|
||||
default = 5
|
||||
}
|
||||
|
||||
variable "health_check_healthy_threshold" {
|
||||
description = "Consecutive successes to mark healthy"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "health_check_unhealthy_threshold" {
|
||||
description = "Consecutive failures to mark unhealthy"
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
# ── Monitoring ──────────────────────────────────────────────────────────────
|
||||
|
||||
variable "enable_monitoring" {
|
||||
description = "Enable Cloud Monitoring alerts and dashboard"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "alert_email" {
|
||||
description = "Email for monitoring notifications"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
Reference in New Issue
Block a user