diff --git a/CLAUDE.md b/CLAUDE.md index a6da8782..57c2c73c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,6 +107,7 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar - `DockerNetworkManager` — ensures bridge networks (cameleer-traefik, cameleer-env-{slug}), connects containers - `DockerEventMonitor` — persistent Docker event stream listener (die, oom, start, stop), updates deployment status - `TraefikLabelBuilder` — generates Traefik Docker labels for path-based or subdomain routing +- `PrometheusLabelBuilder` — generates Prometheus Docker labels (`prometheus.scrape/path/port`) per runtime type for `docker_sd_configs` auto-discovery - `DisabledRuntimeOrchestrator` — no-op when runtime not enabled **storage/** — PostgreSQL repositories (JdbcTemplate) @@ -235,6 +236,7 @@ When deployed via the cameleer-saas platform, this server orchestrates customer - **ConfigMerger** (`core/runtime/ConfigMerger.java`) — pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfig. Three-layer merge: global (application.yml) -> environment (defaultContainerConfig JSONB) -> app (containerConfig JSONB). Includes `runtimeType` (default `"auto"`) and `customArgs` (default `""`). - **TraefikLabelBuilder** (`app/runtime/TraefikLabelBuilder.java`) — generates Traefik Docker labels for path-based (`/{envSlug}/{appSlug}/`) or subdomain-based (`{appSlug}-{envSlug}.{domain}`) routing. Supports strip-prefix and SSL offloading toggles. +- **PrometheusLabelBuilder** (`app/runtime/PrometheusLabelBuilder.java`) — generates Prometheus `docker_sd_configs` labels per resolved runtime type: Spring Boot `/actuator/prometheus:8081`, Quarkus/native `/q/metrics:9000`, plain Java `/metrics:9464`. Labels merged into container metadata alongside Traefik labels at deploy time. - **DockerNetworkManager** (`app/runtime/DockerNetworkManager.java`) — manages two Docker network tiers: - `cameleer-traefik` — shared network; Traefik, server, and all app containers attach here. Server joined via docker-compose with `cameleer3-server` DNS alias. - `cameleer-env-{slug}` — per-environment isolated network; containers in the same environment discover each other via Docker DNS. In SaaS mode, env networks are tenant-scoped: `cameleer-env-{tenantId}-{envSlug}` (overloaded `envNetworkName(tenantId, envSlug)` method) to prevent cross-tenant collisions when multiple tenants have identically-named environments. @@ -298,7 +300,7 @@ In SaaS mode, each tenant's server and its deployed apps are isolated at the Doc # GitNexus — Code Intelligence -This project is indexed by GitNexus as **cameleer3-server** (5967 symbols, 15136 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **cameleer3-server** (5968 symbols, 15141 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java index c35bbb60..21266d68 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java @@ -162,6 +162,7 @@ public class DeploymentExecutor { Map baseEnvVars = buildEnvVars(app, env, config); Map labels = TraefikLabelBuilder.build(app.slug(), env.slug(), tenantId, config); + labels.putAll(PrometheusLabelBuilder.build(resolvedRuntimeType)); List> replicaStates = new ArrayList<>(); List newContainerIds = new ArrayList<>(); diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilder.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilder.java new file mode 100644 index 00000000..abdd3a9a --- /dev/null +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilder.java @@ -0,0 +1,35 @@ +package com.cameleer3.server.app.runtime; + +import java.util.LinkedHashMap; +import java.util.Map; + +public final class PrometheusLabelBuilder { + + private PrometheusLabelBuilder() {} + + public static Map build(String resolvedRuntimeType) { + Map labels = new LinkedHashMap<>(); + labels.put("prometheus.scrape", "true"); + + switch (resolvedRuntimeType) { + case "spring-boot" -> { + labels.put("prometheus.path", "/actuator/prometheus"); + labels.put("prometheus.port", "8081"); + } + case "quarkus", "native" -> { + labels.put("prometheus.path", "/q/metrics"); + labels.put("prometheus.port", "9000"); + } + case "plain-java" -> { + labels.put("prometheus.path", "/metrics"); + labels.put("prometheus.port", "9464"); + } + default -> { + labels.put("prometheus.path", "/actuator/prometheus"); + labels.put("prometheus.port", "8081"); + } + } + + return labels; + } +} diff --git a/cameleer3-server-app/src/test/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilderTest.java b/cameleer3-server-app/src/test/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilderTest.java new file mode 100644 index 00000000..98a31fe5 --- /dev/null +++ b/cameleer3-server-app/src/test/java/com/cameleer3/server/app/runtime/PrometheusLabelBuilderTest.java @@ -0,0 +1,50 @@ +package com.cameleer3.server.app.runtime; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class PrometheusLabelBuilderTest { + + @Test + void springBootLabels() { + Map labels = PrometheusLabelBuilder.build("spring-boot"); + assertEquals("true", labels.get("prometheus.scrape")); + assertEquals("/actuator/prometheus", labels.get("prometheus.path")); + assertEquals("8081", labels.get("prometheus.port")); + } + + @Test + void quarkusLabels() { + Map labels = PrometheusLabelBuilder.build("quarkus"); + assertEquals("true", labels.get("prometheus.scrape")); + assertEquals("/q/metrics", labels.get("prometheus.path")); + assertEquals("9000", labels.get("prometheus.port")); + } + + @Test + void nativeLabels() { + Map labels = PrometheusLabelBuilder.build("native"); + assertEquals("true", labels.get("prometheus.scrape")); + assertEquals("/q/metrics", labels.get("prometheus.path")); + assertEquals("9000", labels.get("prometheus.port")); + } + + @Test + void plainJavaLabels() { + Map labels = PrometheusLabelBuilder.build("plain-java"); + assertEquals("true", labels.get("prometheus.scrape")); + assertEquals("/metrics", labels.get("prometheus.path")); + assertEquals("9464", labels.get("prometheus.port")); + } + + @Test + void unknownDefaultsToSpringBoot() { + Map labels = PrometheusLabelBuilder.build("unknown"); + assertEquals("true", labels.get("prometheus.scrape")); + assertEquals("/actuator/prometheus", labels.get("prometheus.path")); + assertEquals("8081", labels.get("prometheus.port")); + } +}