mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:20:44 +00:00
perf(config): use direct writes for gateway token persistence
This commit is contained in:
@@ -7,7 +7,7 @@ const resolveNodeStartupTlsEnvironmentMock = vi.hoisted(() => vi.fn());
|
||||
const loadConfigMock = vi.hoisted(() => vi.fn());
|
||||
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
|
||||
const resolveGatewayPortMock = vi.hoisted(() => vi.fn(() => 18789));
|
||||
const replaceConfigFileMock = vi.hoisted(() => vi.fn());
|
||||
const writeConfigFileMock = vi.hoisted(() => vi.fn());
|
||||
const resolveIsNixModeMock = vi.hoisted(() => vi.fn(() => false));
|
||||
const resolveSecretInputRefMock = vi.hoisted(() =>
|
||||
vi.fn((): { ref: unknown } => ({ ref: undefined })),
|
||||
@@ -80,7 +80,11 @@ vi.mock("../../config/paths.js", () => ({
|
||||
|
||||
vi.mock("../../commands/gateway-install-token.persist.runtime.js", () => ({
|
||||
readConfigFileSnapshot: readConfigFileSnapshotMock,
|
||||
replaceConfigFile: replaceConfigFileMock,
|
||||
readConfigFileSnapshotForWrite: vi.fn(async () => ({
|
||||
snapshot: await readConfigFileSnapshotMock(),
|
||||
writeOptions: { expectedConfigPath: "/tmp/openclaw.json" },
|
||||
})),
|
||||
writeConfigFile: writeConfigFileMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../config/types.secrets.js", () => ({
|
||||
@@ -172,7 +176,7 @@ describe("runDaemonInstall", () => {
|
||||
resolveNodeStartupTlsEnvironmentMock.mockReset();
|
||||
readConfigFileSnapshotMock.mockReset();
|
||||
resolveGatewayPortMock.mockClear();
|
||||
replaceConfigFileMock.mockReset();
|
||||
writeConfigFileMock.mockReset();
|
||||
resolveIsNixModeMock.mockReset();
|
||||
resolveSecretInputRefMock.mockReset();
|
||||
resolveGatewayAuthMock.mockReset();
|
||||
@@ -251,7 +255,7 @@ describe("runDaemonInstall", () => {
|
||||
expect(actionState.failed).toEqual([]);
|
||||
expect(buildGatewayInstallPlanMock).toHaveBeenCalledTimes(1);
|
||||
expectFirstInstallPlanCallOmitsToken();
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(
|
||||
actionState.warnings.some((warning) =>
|
||||
warning.includes("gateway.auth.token is SecretRef-managed"),
|
||||
@@ -285,13 +289,11 @@ describe("runDaemonInstall", () => {
|
||||
await runDaemonInstall({ json: true });
|
||||
|
||||
expect(actionState.failed).toEqual([]);
|
||||
expect(replaceConfigFileMock).toHaveBeenCalledTimes(1);
|
||||
const writtenConfig = replaceConfigFileMock.mock.calls[0]?.[0] as {
|
||||
nextConfig?: {
|
||||
gateway?: { auth?: { token?: string } };
|
||||
};
|
||||
expect(writeConfigFileMock).toHaveBeenCalledTimes(1);
|
||||
const writtenConfig = writeConfigFileMock.mock.calls[0]?.[0] as {
|
||||
gateway?: { auth?: { token?: string } };
|
||||
};
|
||||
expect(writtenConfig.nextConfig?.gateway?.auth?.token).toBe("minted-token");
|
||||
expect(writtenConfig.gateway?.auth?.token).toBe("minted-token");
|
||||
expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ port: 18789 }),
|
||||
);
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export { readConfigFileSnapshot } from "../config/io.js";
|
||||
export { replaceConfigFile } from "../config/mutate.js";
|
||||
export {
|
||||
readConfigFileSnapshot,
|
||||
readConfigFileSnapshotForWrite,
|
||||
writeConfigFile,
|
||||
} from "../config/io.js";
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveGatewayInstallToken } from "./gateway-install-token.js";
|
||||
|
||||
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
|
||||
const replaceConfigFileMock = vi.hoisted(() => vi.fn());
|
||||
const readConfigFileSnapshotForWriteMock = vi.hoisted(() => vi.fn());
|
||||
const writeConfigFileMock = vi.hoisted(() => vi.fn());
|
||||
const resolveSecretInputRefMock = vi.hoisted(() =>
|
||||
vi.fn((): { ref: unknown } => ({ ref: undefined })),
|
||||
);
|
||||
@@ -30,7 +31,8 @@ const randomTokenMock = vi.hoisted(() => vi.fn(() => "generated-token"));
|
||||
|
||||
vi.mock("./gateway-install-token.persist.runtime.js", () => ({
|
||||
readConfigFileSnapshot: readConfigFileSnapshotMock,
|
||||
replaceConfigFile: replaceConfigFileMock,
|
||||
readConfigFileSnapshotForWrite: readConfigFileSnapshotForWriteMock,
|
||||
writeConfigFile: writeConfigFileMock,
|
||||
}));
|
||||
|
||||
vi.mock("../config/types.secrets.js", () => ({
|
||||
@@ -62,6 +64,10 @@ describe("resolveGatewayInstallToken", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
readConfigFileSnapshotMock.mockResolvedValue({ exists: false, valid: true, config: {} });
|
||||
readConfigFileSnapshotForWriteMock.mockImplementation(async () => ({
|
||||
snapshot: await readConfigFileSnapshotMock(),
|
||||
writeOptions: {},
|
||||
}));
|
||||
resolveSecretInputRefMock.mockReturnValue({ ref: undefined });
|
||||
hasConfiguredSecretInputMock.mockImplementation((value: unknown) => {
|
||||
if (typeof value === "string") {
|
||||
@@ -152,7 +158,7 @@ describe("resolveGatewayInstallToken", () => {
|
||||
expect(result.unavailableReason).toContain("gateway.auth.mode is unset");
|
||||
expect(result.unavailableReason).toContain("openclaw config set gateway.auth.mode token");
|
||||
expect(result.unavailableReason).toContain("openclaw config set gateway.auth.mode password");
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(resolveSecretRefValuesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -170,7 +176,7 @@ describe("resolveGatewayInstallToken", () => {
|
||||
expect(
|
||||
result.warnings.some((message) => message.includes("without saving to config")),
|
||||
).toBeTruthy();
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("persists auto-generated token when requested", async () => {
|
||||
@@ -184,9 +190,8 @@ describe("resolveGatewayInstallToken", () => {
|
||||
});
|
||||
|
||||
expect(result.warnings.some((message) => message.includes("saving to config"))).toBeTruthy();
|
||||
expect(replaceConfigFileMock).toHaveBeenCalledWith({
|
||||
baseHash: undefined,
|
||||
nextConfig: expect.objectContaining({
|
||||
expect(writeConfigFileMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "token",
|
||||
@@ -194,7 +199,8 @@ describe("resolveGatewayInstallToken", () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect.objectContaining({ baseSnapshot: expect.any(Object) }),
|
||||
);
|
||||
});
|
||||
|
||||
it("drops generated plaintext when config changes to SecretRef before persist", async () => {
|
||||
@@ -227,7 +233,7 @@ describe("resolveGatewayInstallToken", () => {
|
||||
expect(
|
||||
result.warnings.some((message) => message.includes("skipping plaintext token persistence")),
|
||||
).toBeTruthy();
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not auto-generate when inferred mode has password SecretRef configured", async () => {
|
||||
@@ -254,7 +260,7 @@ describe("resolveGatewayInstallToken", () => {
|
||||
expect(result.token).toBeUndefined();
|
||||
expect(result.unavailableReason).toBeUndefined();
|
||||
expect(result.warnings.some((message) => message.includes("Auto-generated"))).toBe(false);
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes the install env through to gateway auth resolution", async () => {
|
||||
@@ -286,7 +292,7 @@ describe("resolveGatewayInstallToken", () => {
|
||||
expect(result.token).toBeUndefined();
|
||||
expect(result.unavailableReason).toBeUndefined();
|
||||
expect(result.warnings.some((message) => message.includes("Auto-generated"))).toBe(false);
|
||||
expect(replaceConfigFileMock).not.toHaveBeenCalled();
|
||||
expect(writeConfigFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips token SecretRef resolution when token auth is not required", async () => {
|
||||
|
||||
@@ -9,8 +9,8 @@ import { resolveGatewayAuthToken } from "../gateway/auth-token-resolution.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
readConfigFileSnapshot,
|
||||
replaceConfigFile,
|
||||
readConfigFileSnapshotForWrite,
|
||||
writeConfigFile,
|
||||
} from "./gateway-install-token.persist.runtime.js";
|
||||
import { randomToken } from "./random-token.js";
|
||||
|
||||
@@ -39,7 +39,14 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
||||
warnings: string[];
|
||||
}): Promise<string | undefined> {
|
||||
try {
|
||||
const snapshot = params.configSnapshot ?? (await readConfigFileSnapshot());
|
||||
const prepared =
|
||||
params.configSnapshot && params.configWriteOptions
|
||||
? {
|
||||
snapshot: params.configSnapshot,
|
||||
writeOptions: params.configWriteOptions,
|
||||
}
|
||||
: await readConfigFileSnapshotForWrite();
|
||||
const snapshot = params.configSnapshot ?? prepared.snapshot;
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
params.warnings.push(
|
||||
"Warning: config file exists but is invalid; skipping token persistence.",
|
||||
@@ -57,9 +64,8 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
||||
? undefined
|
||||
: normalizeOptionalString(baseConfig.gateway.auth.token);
|
||||
if (!existingTokenRef && !baseConfigToken) {
|
||||
await replaceConfigFile({
|
||||
baseHash: snapshot.hash,
|
||||
nextConfig: {
|
||||
await writeConfigFile(
|
||||
{
|
||||
...baseConfig,
|
||||
gateway: {
|
||||
...baseConfig.gateway,
|
||||
@@ -70,9 +76,12 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshot: params.configSnapshot,
|
||||
writeOptions: params.configWriteOptions,
|
||||
});
|
||||
{
|
||||
baseSnapshot: snapshot,
|
||||
...prepared.writeOptions,
|
||||
...params.configWriteOptions,
|
||||
},
|
||||
);
|
||||
return params.token;
|
||||
}
|
||||
if (baseConfigToken) {
|
||||
|
||||
Reference in New Issue
Block a user