mirror of https://github.com/garrytan/gstack.git
Merge 7eca138754 into 7c9df1c568
This commit is contained in:
commit
6d28cacf74
|
|
@ -38,6 +38,8 @@ import { spawnSync, spawn, execFileSync, type SpawnSyncReturns, type ChildProces
|
||||||
|
|
||||||
interface GbrainConfig {
|
interface GbrainConfig {
|
||||||
database_url?: string;
|
database_url?: string;
|
||||||
|
engine?: string;
|
||||||
|
database_path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuildGbrainEnvOptions {
|
export interface BuildGbrainEnvOptions {
|
||||||
|
|
@ -80,9 +82,14 @@ export function isTransactionModePooler(url: string): boolean {
|
||||||
* unchanged when:
|
* unchanged when:
|
||||||
* - `GSTACK_RESPECT_ENV_DATABASE_URL=1` (intentional opt-out),
|
* - `GSTACK_RESPECT_ENV_DATABASE_URL=1` (intentional opt-out),
|
||||||
* - the config file is missing or unparseable,
|
* - the config file is missing or unparseable,
|
||||||
* - the config has no `database_url`,
|
* - the config has no `database_url` and is not a local PGLite config,
|
||||||
* - the caller already set DATABASE_URL to the same value.
|
* - the caller already set DATABASE_URL to the same value.
|
||||||
*
|
*
|
||||||
|
* For PGLite configs, there is no DATABASE_URL to seed. In that case we
|
||||||
|
* strip caller-supplied database URLs so Bun's project `.env` autoload
|
||||||
|
* cannot make gbrain probe an unrelated app database and classify a healthy
|
||||||
|
* file-backed brain as `broken-db`.
|
||||||
|
*
|
||||||
* GBRAIN_PREPARE is never set here (#1965): gbrain auto-disables prepared
|
* GBRAIN_PREPARE is never set here (#1965): gbrain auto-disables prepared
|
||||||
* statements on transaction-mode poolers itself, and forcing them on breaks
|
* statements on transaction-mode poolers itself, and forcing them on breaks
|
||||||
* every write with "prepared statement does not exist". A caller-set
|
* every write with "prepared statement does not exist". A caller-set
|
||||||
|
|
@ -109,7 +116,13 @@ export function buildGbrainEnv(opts: BuildGbrainEnvOptions = {}): NodeJS.Process
|
||||||
} catch {
|
} catch {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
if (!cfg.database_url) return out;
|
if (!cfg.database_url) {
|
||||||
|
if (cfg.engine === "pglite" || cfg.database_path) {
|
||||||
|
delete out.DATABASE_URL;
|
||||||
|
delete out.GBRAIN_DATABASE_URL;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
const hadCaller = baseEnv.DATABASE_URL !== undefined;
|
const hadCaller = baseEnv.DATABASE_URL !== undefined;
|
||||||
const alreadyMatch = baseEnv.DATABASE_URL === cfg.database_url;
|
const alreadyMatch = baseEnv.DATABASE_URL === cfg.database_url;
|
||||||
|
|
|
||||||
|
|
@ -70,13 +70,28 @@ describe("buildGbrainEnv", () => {
|
||||||
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns caller env unchanged when config has no database_url field", () => {
|
it("returns caller env unchanged when config has no database_url field and no local-engine signal", () => {
|
||||||
writeFileSync(join(gbrainHome, "config.json"), JSON.stringify({ engine: "pglite" }));
|
writeFileSync(join(gbrainHome, "config.json"), JSON.stringify({ profile: "legacy" }));
|
||||||
const baseEnv = { HOME: home, DATABASE_URL: "postgresql://app/db" };
|
const baseEnv = { HOME: home, DATABASE_URL: "postgresql://app/db" };
|
||||||
const result = buildGbrainEnv({ baseEnv });
|
const result = buildGbrainEnv({ baseEnv });
|
||||||
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips caller database URLs for PGLite configs without a database_url", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(gbrainHome, "config.json"),
|
||||||
|
JSON.stringify({ engine: "pglite", database_path: join(gbrainHome, "gbrain.db") }),
|
||||||
|
);
|
||||||
|
const baseEnv = {
|
||||||
|
HOME: home,
|
||||||
|
DATABASE_URL: "sqlite:///app.db",
|
||||||
|
GBRAIN_DATABASE_URL: "postgresql://wrong/db",
|
||||||
|
};
|
||||||
|
const result = buildGbrainEnv({ baseEnv });
|
||||||
|
expect(result.DATABASE_URL).toBeUndefined();
|
||||||
|
expect(result.GBRAIN_DATABASE_URL).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("honors GBRAIN_HOME when set (config aligned with detectEngineTier)", () => {
|
it("honors GBRAIN_HOME when set (config aligned with detectEngineTier)", () => {
|
||||||
// Move the config to an alternate dir; set GBRAIN_HOME to point at it.
|
// Move the config to an alternate dir; set GBRAIN_HOME to point at it.
|
||||||
const altGbrainHome = join(home, "alt-gbrain");
|
const altGbrainHome = join(home, "alt-gbrain");
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ interface FakeEnv {
|
||||||
*/
|
*/
|
||||||
function makeEnv(opts: {
|
function makeEnv(opts: {
|
||||||
withGbrain?: boolean;
|
withGbrain?: boolean;
|
||||||
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "throws" | "slow";
|
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "throws" | "fail-on-database-url" | "slow";
|
||||||
withConfig?: boolean;
|
withConfig?: boolean;
|
||||||
|
configJson?: Record<string, unknown>;
|
||||||
}): FakeEnv {
|
}): FakeEnv {
|
||||||
const tmp = mkdtempSync(join(tmpdir(), "gbrain-local-status-test-"));
|
const tmp = mkdtempSync(join(tmpdir(), "gbrain-local-status-test-"));
|
||||||
const bindir = join(tmp, "bin");
|
const bindir = join(tmp, "bin");
|
||||||
|
|
@ -79,7 +80,7 @@ function makeEnv(opts: {
|
||||||
if (opts.withConfig) {
|
if (opts.withConfig) {
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
configPath,
|
configPath,
|
||||||
JSON.stringify({ engine: "pglite", database_url: "pglite:///fake" }),
|
JSON.stringify(opts.configJson ?? { engine: "pglite", database_url: "pglite:///fake" }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +103,7 @@ function makeEnv(opts: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFakeGbrainScript(
|
function makeFakeGbrainScript(
|
||||||
behavior: "ok" | "broken-db" | "broken-config" | "throws" | "slow",
|
behavior: "ok" | "broken-db" | "broken-config" | "throws" | "fail-on-database-url" | "slow",
|
||||||
): string {
|
): string {
|
||||||
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
|
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
|
||||||
// the (test-lowered) probe timeout, then would answer fine.
|
// the (test-lowered) probe timeout, then would answer fine.
|
||||||
|
|
@ -135,6 +136,14 @@ if [ "$1" = "--version" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
if [ "$1 $2" = "sources list" ]; then
|
if [ "$1 $2" = "sources list" ]; then
|
||||||
|
if [ "${behavior}" = "fail-on-database-url" ]; then
|
||||||
|
if [ -n "$DATABASE_URL" ] || [ -n "$GBRAIN_DATABASE_URL" ]; then
|
||||||
|
echo "Cannot connect to database: leaked DATABASE_URL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo '{"sources":[]}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
if [ ${exitCode} -eq 0 ]; then
|
if [ ${exitCode} -eq 0 ]; then
|
||||||
echo '{"sources":[]}'
|
echo '{"sources":[]}'
|
||||||
exit 0
|
exit 0
|
||||||
|
|
@ -158,6 +167,8 @@ function applyEnv(env: FakeEnv): () => void {
|
||||||
HOME: process.env.HOME,
|
HOME: process.env.HOME,
|
||||||
PATH: process.env.PATH,
|
PATH: process.env.PATH,
|
||||||
GSTACK_HOME: process.env.GSTACK_HOME,
|
GSTACK_HOME: process.env.GSTACK_HOME,
|
||||||
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
|
GBRAIN_DATABASE_URL: process.env.GBRAIN_DATABASE_URL,
|
||||||
GBRAIN_HOME: process.env.GBRAIN_HOME,
|
GBRAIN_HOME: process.env.GBRAIN_HOME,
|
||||||
GSTACK_GBRAIN_PROBE_TIMEOUT_MS: process.env.GSTACK_GBRAIN_PROBE_TIMEOUT_MS,
|
GSTACK_GBRAIN_PROBE_TIMEOUT_MS: process.env.GSTACK_GBRAIN_PROBE_TIMEOUT_MS,
|
||||||
};
|
};
|
||||||
|
|
@ -231,6 +242,20 @@ describe("lib/gbrain-local-status — status classification", () => {
|
||||||
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not misclassify a PGLite brain as broken-db when caller env leaks DATABASE_URL", () => {
|
||||||
|
env = makeEnv({
|
||||||
|
withGbrain: true,
|
||||||
|
gbrainBehavior: "fail-on-database-url",
|
||||||
|
withConfig: true,
|
||||||
|
configJson: { engine: "pglite", database_path: "local/pglite.db" },
|
||||||
|
});
|
||||||
|
restoreEnv = applyEnv(env);
|
||||||
|
process.env.DATABASE_URL = "sqlite:///app.db";
|
||||||
|
process.env.GBRAIN_DATABASE_URL = "postgresql://wrong/db";
|
||||||
|
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns 'timeout' (not broken-config) when the probe exceeds the deadline (#1964)", () => {
|
it("returns 'timeout' (not broken-config) when the probe exceeds the deadline (#1964)", () => {
|
||||||
env = makeEnv({ withGbrain: true, gbrainBehavior: "slow", withConfig: true });
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "slow", withConfig: true });
|
||||||
restoreEnv = applyEnv(env);
|
restoreEnv = applyEnv(env);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue