Probe tagged-union ListTags shape + surface raw bodies on miss
read/ListTags with an empty body was returning something we couldn't
parse, so every tag lookup silently missed and the CreateTag duplicate
fallback dead-ended with no diagnostic.
Now the tag lookup tries two request styles in order — the per-endpoint
`read/ListTags {}` used by the rest of this file, then the tagged-union
`read {type: "ListTags", params: {}}` used by mbecker20/komodo's
canonical API. If both fail to yield a match, both raw response bodies
get logged so the actual Komodo shape is diagnosable on the next run
without another commit-and-deploy cycle.
This commit is contained in:
@@ -548,27 +548,55 @@ function unwrapTagList(body: unknown): unknown[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the tag list from Komodo. Two request styles are attempted in order:
|
||||||
|
*
|
||||||
|
* 1. `POST /read/ListTags` with an empty body — the per-endpoint style
|
||||||
|
* already used by the rest of this file (see execute/RestartDeployment).
|
||||||
|
* 2. `POST /read` with `{type: "ListTags", params: {}}` — the tagged-union
|
||||||
|
* style used by mbecker20/komodo's canonical API. If your Komodo build
|
||||||
|
* wires up only the tagged-union path, style #1 returns something we
|
||||||
|
* can't parse (empty array or non-tag payload) and we fall through.
|
||||||
|
*
|
||||||
|
* The winning parsed list is returned. The raw response bodies get logged
|
||||||
|
* only when both attempts fail to yield a match, so a fresh Komodo API shape
|
||||||
|
* is diagnosable without spamming stderr on the happy path.
|
||||||
|
*/
|
||||||
|
async function fetchTagsWithFallback(): Promise<{ tags: unknown[]; rawBodies: string[] }> {
|
||||||
|
const attempts: Array<{ path: string; body: unknown }> = [
|
||||||
|
{ path: 'read/ListTags', body: {} },
|
||||||
|
{ path: 'read', body: { type: 'ListTags', params: {} } }
|
||||||
|
];
|
||||||
|
const rawBodies: string[] = [];
|
||||||
|
for (const attempt of attempts) {
|
||||||
|
let res: Response;
|
||||||
|
try {
|
||||||
|
res = await komodoCall(attempt.path, attempt.body);
|
||||||
|
} catch (err) {
|
||||||
|
rawBodies.push(`${attempt.path}: network error — ${(err as Error).message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!res.ok) {
|
||||||
|
const snippet = await res.text().catch(() => '');
|
||||||
|
rawBodies.push(`${attempt.path}: HTTP ${res.status}${snippet ? ` — ${truncate(snippet)}` : ''}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const raw = await res.text().catch(() => '');
|
||||||
|
rawBodies.push(`${attempt.path}: ${truncate(raw)}`);
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const tags = unwrapTagList(parsed);
|
||||||
|
if (tags.length > 0) return { tags, rawBodies };
|
||||||
|
}
|
||||||
|
return { tags: [], rawBodies };
|
||||||
|
}
|
||||||
|
|
||||||
async function findTagByName(name: string): Promise<KomodoTagResult | null> {
|
async function findTagByName(name: string): Promise<KomodoTagResult | null> {
|
||||||
let listRes: Response;
|
const { tags, rawBodies } = await fetchTagsWithFallback();
|
||||||
try {
|
|
||||||
listRes = await komodoCall('read/ListTags', {});
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`[komodoEnsureTag] ListTags network failure for "${name}":`, (err as Error).message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!listRes.ok) {
|
|
||||||
const snippet = await listRes.text().catch(() => '');
|
|
||||||
console.warn(`[komodoEnsureTag] ListTags HTTP ${listRes.status} for "${name}"${snippet ? `: ${truncate(snippet)}` : ''}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let body: unknown;
|
|
||||||
try {
|
|
||||||
body = await listRes.json();
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`[komodoEnsureTag] ListTags non-JSON body for "${name}":`, (err as Error).message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const tags = unwrapTagList(body);
|
|
||||||
for (const t of tags) {
|
for (const t of tags) {
|
||||||
if (!t || typeof t !== 'object') continue;
|
if (!t || typeof t !== 'object') continue;
|
||||||
const tagName = (t as { name?: unknown }).name;
|
const tagName = (t as { name?: unknown }).name;
|
||||||
@@ -576,6 +604,11 @@ async function findTagByName(name: string): Promise<KomodoTagResult | null> {
|
|||||||
const id = extractTagId(t);
|
const id = extractTagId(t);
|
||||||
if (id) return { ok: true, id };
|
if (id) return { ok: true, id };
|
||||||
}
|
}
|
||||||
|
// Miss — dump the raw bodies so the next iteration knows the real shape.
|
||||||
|
console.warn(
|
||||||
|
`[komodoEnsureTag] no matchable tag "${name}" in any ListTags variant. Bodies:\n ` +
|
||||||
|
rawBodies.join('\n ')
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user