fix(openbao): include response body in error messages
Some checks failed
Some checks failed
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:
@@ -29,6 +29,17 @@
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import type { SecretBackendDriver, SecretData, ExternalRef, SecretRefResolver } from './types.js';
|
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 {
|
export interface OpenBaoConfigBase {
|
||||||
url: string;
|
url: string;
|
||||||
mount?: string;
|
mount?: string;
|
||||||
@@ -128,7 +139,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
|
|||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
throw new Error(`OpenBao: secret '${input.name}' not found at ${path}`);
|
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 } };
|
const body = await res.json() as { data?: { data?: SecretData } };
|
||||||
return body.data?.data ?? {};
|
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 }> {
|
async write(input: { name: string; data: SecretData }): Promise<{ externalRef: ExternalRef; storedData: SecretData }> {
|
||||||
const path = this.pathFor(input.name);
|
const path = this.pathFor(input.name);
|
||||||
const res = await this.request('POST', `/v1/${this.mount}/data/${path}`, { data: input.data });
|
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: {} };
|
return { externalRef: `${this.mount}/${path}`, storedData: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +155,7 @@ export class OpenBaoDriver implements SecretBackendDriver {
|
|||||||
const path = this.pathFor(input.name);
|
const path = this.pathFor(input.name);
|
||||||
const res = await this.request('DELETE', `/v1/${this.mount}/metadata/${path}`);
|
const res = await this.request('DELETE', `/v1/${this.mount}/metadata/${path}`);
|
||||||
if (!res.ok && res.status !== 404) {
|
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 listPath = this.pathPrefix === '' ? '' : `${this.pathPrefix}/`;
|
||||||
const res = await this.request('LIST', `/v1/${this.mount}/metadata/${listPath}`);
|
const res = await this.request('LIST', `/v1/${this.mount}/metadata/${listPath}`);
|
||||||
if (res.status === 404) return [];
|
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 body = await res.json() as { data?: { keys?: string[] } };
|
||||||
const keys = body.data?.keys ?? [];
|
const keys = body.data?.keys ?? [];
|
||||||
return keys
|
return keys
|
||||||
|
|||||||
Reference in New Issue
Block a user