57dc91585d
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.
603 lines
19 KiB
Terraform
603 lines
19 KiB
Terraform
# ─────────────────────────────────────────────────────────────────────────────
|
||
# OCI Provider – Full implementation for Claude Code Agent Monitor
|
||
#
|
||
# Architecture:
|
||
# VCN → Container Instances (blue/green) → File Storage Service
|
||
# → Flexible Load Balancer → OCI Monitoring & Notifications
|
||
#
|
||
# OCI Container Instances provide a serverless container runtime.
|
||
# File Storage Service (FSS) delivers NFS for SQLite persistence.
|
||
# Flexible Load Balancer supports WebSocket, SSL, and weighted backends.
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
provider "oci" {
|
||
region = var.region
|
||
}
|
||
|
||
# ── Data sources ────────────────────────────────────────────────────────────
|
||
|
||
data "oci_identity_availability_domains" "ads" {
|
||
compartment_id = var.compartment_id
|
||
}
|
||
|
||
data "oci_identity_tenancy" "current" {
|
||
tenancy_id = var.tenancy_id
|
||
}
|
||
|
||
# ── Locals ──────────────────────────────────────────────────────────────────
|
||
|
||
locals {
|
||
name_prefix = lower(replace("${var.project_name}-${var.environment}", "_", "-"))
|
||
ad_name = data.oci_identity_availability_domains.ads.availability_domains[0].name
|
||
|
||
common_tags = {
|
||
"project" = var.project_name
|
||
"environment" = var.environment
|
||
"managed_by" = "terraform"
|
||
"cloud_provider" = "oci"
|
||
"repository" = "Claude-Code-Agent-Monitor"
|
||
}
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# VCN (Virtual Cloud Network)
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_core_vcn" "main" {
|
||
compartment_id = var.compartment_id
|
||
cidr_blocks = [var.vpc_cidr]
|
||
display_name = "${local.name_prefix}-vcn"
|
||
dns_label = replace(substr(local.name_prefix, 0, 15), "-", "")
|
||
|
||
freeform_tags = local.common_tags
|
||
|
||
lifecycle {
|
||
prevent_destroy = false
|
||
}
|
||
}
|
||
|
||
# Internet Gateway
|
||
resource "oci_core_internet_gateway" "main" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-igw"
|
||
enabled = true
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
# NAT Gateway
|
||
resource "oci_core_nat_gateway" "main" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-nat"
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
# Service Gateway
|
||
resource "oci_core_service_gateway" "main" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-sgw"
|
||
|
||
services {
|
||
service_id = data.oci_core_services.all.services[0].id
|
||
}
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
data "oci_core_services" "all" {
|
||
filter {
|
||
name = "name"
|
||
values = ["All .* Services In Oracle Services Network"]
|
||
regex = true
|
||
}
|
||
}
|
||
|
||
# ── Route tables ────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_core_route_table" "public" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-public-rt"
|
||
|
||
route_rules {
|
||
network_entity_id = oci_core_internet_gateway.main.id
|
||
destination = "0.0.0.0/0"
|
||
destination_type = "CIDR_BLOCK"
|
||
}
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_core_route_table" "private" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-private-rt"
|
||
|
||
route_rules {
|
||
network_entity_id = oci_core_nat_gateway.main.id
|
||
destination = "0.0.0.0/0"
|
||
destination_type = "CIDR_BLOCK"
|
||
}
|
||
|
||
route_rules {
|
||
network_entity_id = oci_core_service_gateway.main.id
|
||
destination = data.oci_core_services.all.services[0].cidr_block
|
||
destination_type = "SERVICE_CIDR_BLOCK"
|
||
}
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
# ── Security lists ──────────────────────────────────────────────────────────
|
||
|
||
resource "oci_core_security_list" "public" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-public-sl"
|
||
|
||
ingress_security_rules {
|
||
protocol = "6" # TCP
|
||
source = "0.0.0.0/0"
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = 443
|
||
max = 443
|
||
}
|
||
}
|
||
|
||
ingress_security_rules {
|
||
protocol = "6"
|
||
source = "0.0.0.0/0"
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = 80
|
||
max = 80
|
||
}
|
||
}
|
||
|
||
egress_security_rules {
|
||
protocol = "all"
|
||
destination = "0.0.0.0/0"
|
||
destination_type = "CIDR_BLOCK"
|
||
}
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_core_security_list" "private" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
display_name = "${local.name_prefix}-private-sl"
|
||
|
||
ingress_security_rules {
|
||
protocol = "6"
|
||
source = var.vpc_cidr
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = var.app_port
|
||
max = var.app_port
|
||
}
|
||
}
|
||
|
||
ingress_security_rules {
|
||
protocol = "6"
|
||
source = var.vpc_cidr
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = var.mcp_port
|
||
max = var.mcp_port
|
||
}
|
||
}
|
||
|
||
# NFS (FSS)
|
||
ingress_security_rules {
|
||
protocol = "6"
|
||
source = var.vpc_cidr
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = 2048
|
||
max = 2050
|
||
}
|
||
}
|
||
|
||
ingress_security_rules {
|
||
protocol = "6"
|
||
source = var.vpc_cidr
|
||
source_type = "CIDR_BLOCK"
|
||
tcp_options {
|
||
min = 111
|
||
max = 111
|
||
}
|
||
}
|
||
|
||
ingress_security_rules {
|
||
protocol = "17" # UDP
|
||
source = var.vpc_cidr
|
||
source_type = "CIDR_BLOCK"
|
||
udp_options {
|
||
min = 111
|
||
max = 111
|
||
}
|
||
}
|
||
|
||
egress_security_rules {
|
||
protocol = "all"
|
||
destination = "0.0.0.0/0"
|
||
destination_type = "CIDR_BLOCK"
|
||
}
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
# ── Subnets ─────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_core_subnet" "public" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
cidr_block = var.public_subnet_cidrs[0]
|
||
display_name = "${local.name_prefix}-public"
|
||
dns_label = "pub"
|
||
route_table_id = oci_core_route_table.public.id
|
||
security_list_ids = [oci_core_security_list.public.id]
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_core_subnet" "private" {
|
||
compartment_id = var.compartment_id
|
||
vcn_id = oci_core_vcn.main.id
|
||
cidr_block = var.private_subnet_cidrs[0]
|
||
display_name = "${local.name_prefix}-private"
|
||
dns_label = "priv"
|
||
route_table_id = oci_core_route_table.private.id
|
||
security_list_ids = [oci_core_security_list.private.id]
|
||
prohibit_public_ip_on_vnic = true
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# File Storage Service (FSS) – NFS for SQLite
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_file_storage_file_system" "main" {
|
||
compartment_id = var.compartment_id
|
||
availability_domain = local.ad_name
|
||
display_name = "${local.name_prefix}-data"
|
||
|
||
freeform_tags = local.common_tags
|
||
|
||
lifecycle {
|
||
prevent_destroy = true
|
||
}
|
||
}
|
||
|
||
resource "oci_file_storage_mount_target" "main" {
|
||
compartment_id = var.compartment_id
|
||
availability_domain = local.ad_name
|
||
subnet_id = oci_core_subnet.private.id
|
||
display_name = "${local.name_prefix}-mt"
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_file_storage_export_set" "main" {
|
||
mount_target_id = oci_file_storage_mount_target.main.id
|
||
display_name = "${local.name_prefix}-exports"
|
||
max_fs_stat_bytes = var.storage_size_gb * 1073741824 # GiB → bytes
|
||
}
|
||
|
||
resource "oci_file_storage_export" "main" {
|
||
export_set_id = oci_file_storage_export_set.main.id
|
||
file_system_id = oci_file_storage_file_system.main.id
|
||
path = "/appdata"
|
||
|
||
export_options {
|
||
source = var.private_subnet_cidrs[0]
|
||
access = "READ_WRITE"
|
||
identity_squash = "NONE"
|
||
require_privileged_source_port = false
|
||
}
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Container Instances (Blue / Green)
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_container_instances_container_instance" "blue" {
|
||
compartment_id = var.compartment_id
|
||
availability_domain = local.ad_name
|
||
display_name = "${local.name_prefix}-blue"
|
||
|
||
shape = "CI.Standard.E4.Flex"
|
||
shape_config {
|
||
ocpus = var.cpu / 1000.0
|
||
memory_in_gbs = var.memory / 1024.0
|
||
}
|
||
|
||
vnics {
|
||
subnet_id = oci_core_subnet.private.id
|
||
is_public_ip_assigned = false
|
||
}
|
||
|
||
containers {
|
||
display_name = "app"
|
||
image_url = var.app_container_image
|
||
|
||
environment_variables = var.environment_variables
|
||
|
||
health_checks {
|
||
health_check_type = "HTTP"
|
||
port = var.app_port
|
||
path = var.health_check_path
|
||
interval_in_seconds = 30
|
||
timeout_in_seconds = 5
|
||
}
|
||
|
||
resource_config {
|
||
vcpus_limit = var.cpu / 1000.0
|
||
memory_limit_in_gbs = var.memory / 1024.0
|
||
}
|
||
|
||
volume_mounts {
|
||
mount_path = "/app/data"
|
||
volume_name = "app-data"
|
||
is_read_only = false
|
||
}
|
||
}
|
||
|
||
dynamic "containers" {
|
||
for_each = var.mcp_container_image != "" ? [1] : []
|
||
content {
|
||
display_name = "mcp-sidecar"
|
||
image_url = var.mcp_container_image
|
||
|
||
environment_variables = {
|
||
NODE_ENV = "production"
|
||
MCP_PORT = tostring(var.mcp_port)
|
||
}
|
||
|
||
resource_config {
|
||
vcpus_limit = 0.25
|
||
memory_limit_in_gbs = 0.25
|
||
}
|
||
}
|
||
}
|
||
|
||
# NOTE: OCI Container Instances only support EMPTYDIR and CONFIGFILE volume
|
||
# types. For persistent NFS (FSS) storage, mount via the container entrypoint
|
||
# using the mount target IP from oci_file_storage_mount_target.main, or
|
||
# migrate to OCI Kubernetes Engine (OKE) which supports NFS PersistentVolumes.
|
||
volumes {
|
||
name = "app-data"
|
||
volume_type = "EMPTYDIR"
|
||
backing_store = "EPHEMERAL_STORAGE"
|
||
}
|
||
|
||
freeform_tags = merge(local.common_tags, {
|
||
deployment_slot = "blue"
|
||
})
|
||
|
||
lifecycle {
|
||
ignore_changes = [
|
||
freeform_tags["last_deployed"],
|
||
]
|
||
}
|
||
}
|
||
|
||
resource "oci_container_instances_container_instance" "green" {
|
||
count = var.active_deployment_slot == "green" || var.green_weight > 0 ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
availability_domain = local.ad_name
|
||
display_name = "${local.name_prefix}-green"
|
||
|
||
shape = "CI.Standard.E4.Flex"
|
||
shape_config {
|
||
ocpus = var.cpu / 1000.0
|
||
memory_in_gbs = var.memory / 1024.0
|
||
}
|
||
|
||
vnics {
|
||
subnet_id = oci_core_subnet.private.id
|
||
is_public_ip_assigned = false
|
||
}
|
||
|
||
containers {
|
||
display_name = "app"
|
||
image_url = var.app_container_image
|
||
|
||
environment_variables = var.environment_variables
|
||
|
||
health_checks {
|
||
health_check_type = "HTTP"
|
||
port = var.app_port
|
||
path = var.health_check_path
|
||
interval_in_seconds = 30
|
||
timeout_in_seconds = 5
|
||
}
|
||
|
||
resource_config {
|
||
vcpus_limit = var.cpu / 1000.0
|
||
memory_limit_in_gbs = var.memory / 1024.0
|
||
}
|
||
|
||
volume_mounts {
|
||
mount_path = "/app/data"
|
||
volume_name = "app-data"
|
||
is_read_only = false
|
||
}
|
||
}
|
||
|
||
# NOTE: OCI Container Instances only support EMPTYDIR and CONFIGFILE volume
|
||
# types. See blue instance comment for FSS mounting guidance.
|
||
volumes {
|
||
name = "app-data"
|
||
volume_type = "EMPTYDIR"
|
||
backing_store = "EPHEMERAL_STORAGE"
|
||
}
|
||
|
||
freeform_tags = merge(local.common_tags, {
|
||
deployment_slot = "green"
|
||
})
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Flexible Load Balancer
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_load_balancer_load_balancer" "main" {
|
||
compartment_id = var.compartment_id
|
||
display_name = "${local.name_prefix}-lb"
|
||
shape = "flexible"
|
||
|
||
shape_details {
|
||
minimum_bandwidth_in_mbps = var.environment == "production" ? 100 : 10
|
||
maximum_bandwidth_in_mbps = var.environment == "production" ? 1000 : 100
|
||
}
|
||
|
||
subnet_ids = [oci_core_subnet.public.id]
|
||
|
||
is_private = false
|
||
|
||
freeform_tags = local.common_tags
|
||
|
||
lifecycle {
|
||
prevent_destroy = false
|
||
}
|
||
}
|
||
|
||
# Backend set with health check
|
||
resource "oci_load_balancer_backend_set" "app" {
|
||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||
name = "${local.name_prefix}-app-bs"
|
||
policy = "ROUND_ROBIN"
|
||
|
||
session_persistence_configuration {
|
||
cookie_name = "CCAM_SESSION"
|
||
is_secure = true
|
||
}
|
||
|
||
health_checker {
|
||
protocol = "HTTP"
|
||
port = var.app_port
|
||
url_path = var.health_check_path
|
||
return_code = 200
|
||
interval_ms = var.health_check_interval * 1000
|
||
timeout_in_millis = var.health_check_timeout * 1000
|
||
retries = var.health_check_unhealthy_threshold
|
||
}
|
||
}
|
||
|
||
# Blue backend
|
||
resource "oci_load_balancer_backend" "blue" {
|
||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||
backendset_name = oci_load_balancer_backend_set.app.name
|
||
ip_address = oci_container_instances_container_instance.blue.vnics[0].private_ip
|
||
port = var.app_port
|
||
weight = var.blue_weight
|
||
}
|
||
|
||
# Green backend
|
||
resource "oci_load_balancer_backend" "green" {
|
||
count = length(oci_container_instances_container_instance.green) > 0 ? 1 : 0
|
||
|
||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||
backendset_name = oci_load_balancer_backend_set.app.name
|
||
ip_address = oci_container_instances_container_instance.green[0].vnics[0].private_ip
|
||
port = var.app_port
|
||
weight = var.green_weight
|
||
}
|
||
|
||
# HTTP listener
|
||
resource "oci_load_balancer_listener" "http" {
|
||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||
name = "${local.name_prefix}-http"
|
||
default_backend_set_name = oci_load_balancer_backend_set.app.name
|
||
port = 80
|
||
protocol = "HTTP"
|
||
|
||
connection_configuration {
|
||
idle_timeout_in_seconds = 300 # WebSocket support
|
||
}
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# OCI Monitoring – Alarms and Notifications
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
resource "oci_ons_notification_topic" "alerts" {
|
||
count = var.enable_monitoring ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
name = "${local.name_prefix}-alerts"
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_ons_subscription" "email" {
|
||
count = var.enable_monitoring && var.alert_email != "" ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
topic_id = oci_ons_notification_topic.alerts[0].id
|
||
protocol = "EMAIL"
|
||
endpoint = var.alert_email
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_monitoring_alarm" "lb_unhealthy" {
|
||
count = var.enable_monitoring ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
display_name = "${local.name_prefix}-unhealthy-backends"
|
||
namespace = "oci_lbaas"
|
||
query = "UnHealthyBackendCount[1m]{resourceId = \"${oci_load_balancer_load_balancer.main.id}\"}.max() > 0"
|
||
severity = "CRITICAL"
|
||
is_enabled = true
|
||
pending_duration = "PT5M"
|
||
|
||
destinations = var.alert_email != "" ? [oci_ons_notification_topic.alerts[0].id] : []
|
||
|
||
message_format = "ONS_OPTIMIZED"
|
||
body = "Unhealthy backends detected for ${local.name_prefix} load balancer"
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_monitoring_alarm" "lb_5xx" {
|
||
count = var.enable_monitoring ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
display_name = "${local.name_prefix}-high-5xx"
|
||
namespace = "oci_lbaas"
|
||
query = "HttpResponses5xx[1m]{resourceId = \"${oci_load_balancer_load_balancer.main.id}\"}.sum() > 10"
|
||
severity = "WARNING"
|
||
is_enabled = true
|
||
pending_duration = "PT5M"
|
||
|
||
destinations = var.alert_email != "" ? [oci_ons_notification_topic.alerts[0].id] : []
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|
||
|
||
resource "oci_monitoring_alarm" "high_latency" {
|
||
count = var.enable_monitoring ? 1 : 0
|
||
|
||
compartment_id = var.compartment_id
|
||
display_name = "${local.name_prefix}-high-latency"
|
||
namespace = "oci_lbaas"
|
||
query = "BackendTimeFirstByte[1m]{resourceId = \"${oci_load_balancer_load_balancer.main.id}\"}.percentile(0.99) > 2000"
|
||
severity = "WARNING"
|
||
is_enabled = true
|
||
pending_duration = "PT5M"
|
||
|
||
destinations = var.alert_email != "" ? [oci_ons_notification_topic.alerts[0].id] : []
|
||
|
||
freeform_tags = local.common_tags
|
||
}
|