fix(openbao): include response body in error messages
Some checks failed
CI/CD / lint (push) Successful in 52s
CI/CD / typecheck (push) Successful in 51s
CI/CD / test (push) Successful in 1m4s
CI/CD / smoke (push) Failing after 1m36s
CI/CD / build (push) Successful in 2m19s
CI/CD / publish (push) Has been skipped

Debugging the wizard migration flow, every OpenBao error was just
`HTTP 403` with no context. The response body often carries the actual
reason (missing capability, specific path, namespace mismatch), so
surfacing it makes operator debugging a one-step task. Added a shared
bodyText() helper that trims huge HTML error pages to 400 chars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-04-20 21:01:03 +01:00
parent 72e49f719f
commit bf312850b5

View File

@@ -29,6 +29,17 @@
import { readFile } from 'node:fs/promises';
import type { SecretBackendDriver, SecretData, ExternalRef, SecretRefResolver } from './types.js';
/** Best-effort read of a response body for error messages. Empty on parse failure. */
async function bodyText(res: Response): Promise<string> {
try {
const text = await res.text();
// Trim huge HTML error pages to the first 400 chars
return text.length > 400 ? `${text.slice(0, 400)}` : text;
} catch {
return '';
}
}
export interface OpenBaoConfigBase {
url: string;
mount?: string;
@@ -128,7 +139,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
if (res.status === 404) {
throw new Error(`OpenBao: secret '${input.name}' not found at ${path}`);
}
if (!res.ok) throw new Error(`OpenBao read ${path}: HTTP ${res.status}`);
if (!res.ok) throw new Error(`OpenBao read ${path}: HTTP ${res.status} ${await bodyText(res)}`);
const body = await res.json() as { data?: { data?: SecretData } };
return body.data?.data ?? {};
}
@@ -136,7 +147,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
async write(input: { name: string; data: SecretData }): Promise<{ externalRef: ExternalRef; storedData: SecretData }> {
const path = this.pathFor(input.name);
const res = await this.request('POST', `/v1/${this.mount}/data/${path}`, { data: input.data });
if (!res.ok) throw new Error(`OpenBao write ${path}: HTTP ${res.status}`);
if (!res.ok) throw new Error(`OpenBao write ${path}: HTTP ${res.status} ${await bodyText(res)}`);
return { externalRef: `${this.mount}/${path}`, storedData: {} };
}
@@ -144,7 +155,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
const path = this.pathFor(input.name);
const res = await this.request('DELETE', `/v1/${this.mount}/metadata/${path}`);
if (!res.ok && res.status !== 404) {
throw new Error(`OpenBao delete ${path}: HTTP ${res.status}`);
throw new Error(`OpenBao delete ${path}: HTTP ${res.status} ${await bodyText(res)}`);
}
}
@@ -152,7 +163,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
const listPath = this.pathPrefix === '' ? '' : `${this.pathPrefix}/`;
const res = await this.request('LIST', `/v1/${this.mount}/metadata/${listPath}`);
if (res.status === 404) return [];
if (!res.ok) throw new Error(`OpenBao list: HTTP ${res.status}`);
if (!res.ok) throw new Error(`OpenBao list: HTTP ${res.status} ${await bodyText(res)}`);
const body = await res.json() as { data?: { keys?: string[] } };
const keys = body.data?.keys ?? [];
return keys