diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index efb50cd5..fbd86a3f 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -113,7 +113,7 @@ test("hwlab-cli client auth status exposes local session state and next commands assert.equal(JSON.stringify(mismatch.payload.nextCommands).includes("run-bun.mjs"), false); }); -test("hwlab-cli reuses public v02 session when runtime namespace resolves in-cluster endpoint", async () => { +test("hwlab-cli reuses public v02 session when runtime namespace resolves host endpoint", async () => { const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-runtime-scope-")); await runHwlabCli(["client", "auth", "login", "--base-url", "http://74.48.78.17:19666", "--username", "admin", "--password", "pw"], { cwd, @@ -131,13 +131,28 @@ test("hwlab-cli reuses public v02 session when runtime namespace resolves in-clu }); assert.equal(status.exitCode, 0); - assert.equal(status.payload.baseUrl, "http://hwlab-cloud-web.hwlab-v02.svc.cluster.local:8080"); + assert.equal(status.payload.baseUrl, "http://74.48.78.17:19666"); assert.equal(status.payload.localSession.usable, true); assert.equal(status.payload.localSession.ignoredReason, null); - assert.equal(status.payload.localSession.baseUrlMatch, "runtime_scope"); + assert.equal(status.payload.localSession.baseUrlMatch, "exact"); assert.equal(status.payload.localSession.baseUrl, "http://74.48.78.17:19666"); }); +test("hwlab-cli resolves runtime namespace to service DNS inside kubernetes", async () => { + const result = await runHwlabCli(["client", "auth", "status"], { + env: { + HWLAB_RUNTIME_NAMESPACE: "hwlab-v02", + HWLAB_RUNTIME_LANE: "v02", + KUBERNETES_SERVICE_HOST: "10.43.0.1", + POD_NAMESPACE: "hwlab-v02" + } + }); + + assert.equal(result.exitCode, 0); + assert.equal(result.payload.baseUrl, "http://hwlab-cloud-web.hwlab-v02.svc.cluster.local:8080"); + assert.equal(result.payload.runtimeEndpoint.source, "runtime-namespace"); +}); + test("hwlab-cli client group help is visible and does not issue HTTP requests", async () => { let fetchCount = 0; const fetchImpl = async () => { diff --git a/tools/src/runtime-endpoint-resolver.ts b/tools/src/runtime-endpoint-resolver.ts index 5232dbc7..e2e48496 100644 --- a/tools/src/runtime-endpoint-resolver.ts +++ b/tools/src/runtime-endpoint-resolver.ts @@ -132,7 +132,7 @@ export function runtimeHints(parsed: ParsedArgs = {}, env: EnvLike = {}): Runtim } function endpointCandidates(kind: RuntimeEndpointKind, parsed: ParsedArgs, env: EnvLike, hints: RuntimeHints): EndpointCandidate[] { - const namespaceUrl = hints.namespace ? serviceUrlForNamespace(kind, hints.namespace, parsed, env) : null; + const namespaceUrl = hints.namespace ? runtimeUrlForNamespace(kind, hints.namespace, parsed, env) : null; if (kind === "web") { return withLocalDebugOverride([ candidate(firstText(env.HWLAB_RUNTIME_WEB_URL, env.HWLAB_CURRENT_WEB_URL), "runtime-env", "HWLAB_RUNTIME_WEB_URL"), @@ -176,6 +176,23 @@ function endpointLocked(env: EnvLike) { return [env.HWLAB_RUNTIME_ENDPOINT_LOCKED, env.HWLAB_DEVICE_POD_RUNTIME_LOCKED, env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME].some((value) => ["1", "true", "yes", "locked"].includes(firstText(value).toLowerCase())); } +function runtimeUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) { + if (runningInsideKubernetes(env)) return serviceUrlForNamespace(kind, namespace, parsed, env); + return publicUrlForNamespace(kind, namespace); +} + +function runningInsideKubernetes(env: EnvLike) { + return Boolean(firstText(env.KUBERNETES_SERVICE_HOST, env.KUBERNETES_PORT, env.POD_NAMESPACE, env.KUBERNETES_NAMESPACE)); +} + +function publicUrlForNamespace(kind: RuntimeEndpointKind, namespace: string) { + const lane = laneForNamespace(namespace); + if (lane === "v02") return kind === "web" ? "http://74.48.78.17:19666" : "http://74.48.78.17:19667"; + if (lane === "prod") return kind === "web" ? "http://74.48.78.17:18666" : "http://74.48.78.17:18667"; + if (lane === "dev") return kind === "web" ? "http://74.48.78.17:17666" : "http://74.48.78.17:17667"; + return null; +} + function serviceUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) { if (kind === "web") { const serviceName = firstText(parsed.webServiceName, env.HWLAB_CLOUD_WEB_SERVICE_NAME) || "hwlab-cloud-web";