fix: verified-against-live Core v2.3.3 shapes
- login: {type: LoginLocalUser, params} + data.jwt extraction
- inspect: server-scoped objects use {server, container|image|network|volume}
- search_logs: terms array + per-type params (deployment/container/stack/swarm_service)
Verified via 15-test read-only pass against 10.10.2.114:9120
This commit is contained in:
+13
-3
@@ -47,9 +47,13 @@ export class KomodoClient {
|
|||||||
const res = await fetch(`${this.baseUrl}/auth/login`, {
|
const res = await fetch(`${this.baseUrl}/auth/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
// Komodo Core v2.3.3 login shape (verified live): {type, params}
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: this.username,
|
type: "LoginLocalUser",
|
||||||
password: this.password,
|
params: {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(30000),
|
||||||
});
|
});
|
||||||
@@ -57,7 +61,13 @@ export class KomodoClient {
|
|||||||
const text = await res.text().catch(() => "");
|
const text = await res.text().catch(() => "");
|
||||||
throw new Error(`Komodo login failed (${res.status}): ${text}`);
|
throw new Error(`Komodo login failed (${res.status}): ${text}`);
|
||||||
}
|
}
|
||||||
this.token = await res.text();
|
// Response shape: {type, data: {jwt}} — NOT raw text
|
||||||
|
const body = (await res.json()) as { data?: { jwt?: string } };
|
||||||
|
const jwt = body?.data?.jwt;
|
||||||
|
if (!jwt) {
|
||||||
|
throw new Error(`Komodo login succeeded but no data.jwt in response`);
|
||||||
|
}
|
||||||
|
this.token = jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rpc(
|
async rpc(
|
||||||
|
|||||||
+11
-3
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { KomodoClient } from "../komodo-client.js";
|
import { KomodoClient } from "../komodo-client.js";
|
||||||
import { INSPECT_REQUEST_MAP } from "../types.js";
|
import { INSPECT_REQUEST_MAP, INSPECT_PARAM_KEY } from "../types.js";
|
||||||
|
|
||||||
const InspectType = z.enum([
|
const InspectType = z.enum([
|
||||||
"container", "image", "network", "volume",
|
"container", "image", "network", "volume",
|
||||||
@@ -33,8 +33,16 @@ export async function handleInspect(
|
|||||||
throw new Error(`Unknown inspect type: ${inspect_type}`);
|
throw new Error(`Unknown inspect type: ${inspect_type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: Record<string, unknown> = { id };
|
const params: Record<string, unknown> = {};
|
||||||
if (server) params.server = server;
|
const paramKey = server ? INSPECT_PARAM_KEY[inspect_type] : undefined;
|
||||||
|
if (paramKey) {
|
||||||
|
// Server-scoped Docker objects: {server, <container|image|network|volume>}
|
||||||
|
params.server = server;
|
||||||
|
params[paramKey] = id;
|
||||||
|
} else {
|
||||||
|
params.id = id;
|
||||||
|
if (server) params.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await client.rpc("read", requestName, params);
|
const result = await client.rpc("read", requestName, params);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,18 @@ export const searchLogsInputSchema = {
|
|||||||
id: z.string().describe("Resource ID or name"),
|
id: z.string().describe("Resource ID or name"),
|
||||||
query: z.string().describe("Search query to filter log lines"),
|
query: z.string().describe("Search query to filter log lines"),
|
||||||
tail: z.number().optional().describe("Number of recent log lines to return"),
|
tail: z.number().optional().describe("Number of recent log lines to return"),
|
||||||
|
server: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("Server name (required for container logs)"),
|
||||||
|
swarm: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("Swarm name (required for swarm_service logs)"),
|
||||||
|
services: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe("Service names (required for stack logs)"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function handleSearchLogs(
|
export async function handleSearchLogs(
|
||||||
@@ -19,19 +31,45 @@ export async function handleSearchLogs(
|
|||||||
id: string;
|
id: string;
|
||||||
query: string;
|
query: string;
|
||||||
tail?: number;
|
tail?: number;
|
||||||
|
server?: string;
|
||||||
|
swarm?: string;
|
||||||
|
services?: string[];
|
||||||
},
|
},
|
||||||
client: KomodoClient,
|
client: KomodoClient,
|
||||||
): Promise<{ content: { type: "text"; text: string }[] }> {
|
): Promise<{ content: { type: "text"; text: string }[] }> {
|
||||||
const { resource_type, id, query, tail } = args;
|
const { resource_type, id, query, tail, server, swarm, services } = args;
|
||||||
|
|
||||||
const requestName = SEARCH_LOG_REQUEST_MAP[resource_type];
|
const requestName = SEARCH_LOG_REQUEST_MAP[resource_type];
|
||||||
if (!requestName) {
|
if (!requestName) {
|
||||||
throw new Error(`No log search endpoint for resource type: ${resource_type}`);
|
throw new Error(`No log search endpoint for resource type: ${resource_type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: Record<string, unknown> = { id, query };
|
// Core log search shapes are not uniform per docs.rs:
|
||||||
|
// - deployment: {deployment, terms, tail?}
|
||||||
|
// - container: {server, container, terms, tail?}
|
||||||
|
// - stack: {stack, services, terms, tail?}
|
||||||
|
// - swarm_service: {swarm, service, terms, tail?}
|
||||||
|
const params: Record<string, unknown> = { terms: [query] };
|
||||||
if (tail !== undefined) params.tail = tail;
|
if (tail !== undefined) params.tail = tail;
|
||||||
|
|
||||||
|
switch (resource_type) {
|
||||||
|
case "deployment":
|
||||||
|
params.deployment = id;
|
||||||
|
break;
|
||||||
|
case "container":
|
||||||
|
params.server = server;
|
||||||
|
params.container = id;
|
||||||
|
break;
|
||||||
|
case "stack":
|
||||||
|
params.stack = id;
|
||||||
|
params.services = services;
|
||||||
|
break;
|
||||||
|
case "swarm_service":
|
||||||
|
params.swarm = swarm;
|
||||||
|
params.service = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await client.rpc("read", requestName, params);
|
const result = await client.rpc("read", requestName, params);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+12
-1
@@ -449,7 +449,18 @@ export const SUMMARY_REQUEST_MAP: Partial<Record<ResourceType, string>> = {
|
|||||||
action: "GetActionsSummary",
|
action: "GetActionsSummary",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Maps inspect type to the inspect RPC request name
|
// Maps inspect type to the inspect RPC request name.
|
||||||
|
// Server-scoped Docker objects also need an object param key (see
|
||||||
|
// INSPECT_PARAM_KEY below): Core v2.3.3 shapes are {server, container},
|
||||||
|
// {server, image}, {server, network}, {server, volume} — sending
|
||||||
|
// {id, server} fails with "duplicate field `server`".
|
||||||
|
export const INSPECT_PARAM_KEY: Record<string, string> = {
|
||||||
|
container: "container",
|
||||||
|
image: "image",
|
||||||
|
network: "network",
|
||||||
|
volume: "volume",
|
||||||
|
};
|
||||||
|
|
||||||
export const INSPECT_REQUEST_MAP: Record<string, string> = {
|
export const INSPECT_REQUEST_MAP: Record<string, string> = {
|
||||||
container: "InspectContainer",
|
container: "InspectContainer",
|
||||||
image: "InspectImage",
|
image: "InspectImage",
|
||||||
|
|||||||
Reference in New Issue
Block a user