History

backup

Rhys Sullivan committed 8 months ago

Commit c8efcda1 parent 7ce9b9b2 files · +120 −15Browse files

Unified · Split

2 files
  1. packages/control-plane/src/runtime/legacy-postgres-migration.test.ts +29 −4
  2. packages/control-plane/src/runtime/legacy-postgres-migration.ts +91 −11
packages/control-plane/src/runtime/legacy-postgres-migration.test.ts+29−4
@@ -1,6 +1,6 @@
1−import { existsSync, mkdtempSync } from "node:fs";
1+import { existsSync, mkdtempSync, readdirSync } from "node:fs";
22 import { tmpdir } from "node:os";
3−import { join } from "node:path";
3+import { basename, dirname, join } from "node:path";
44
55 import { PGlite } from "@electric-sql/pglite";
66 import { NodeFileSystem } from "@effect/platform-node";
@@ -39,6 +39,16 @@ const UPDATED_AT = 1_700_000_000_500; · 32 unmodified lines
3939 const makeTempDir = (prefix: string) =>
4040 mkdtempSync(join(tmpdir(), prefix));
4141
42+const findLegacyBackupPath = (legacyLocalDataDir: string): string | null => {
43+ const parentDirectory = dirname(legacyLocalDataDir);
44+ const backupPrefix = `${basename(legacyLocalDataDir)}.migrated-backup-`;
45+ const backupEntry = readdirSync(parentDirectory).find((entry) =>
46+ entry.startsWith(backupPrefix),
47+ );
48+
49+ return backupEntry ? join(parentDirectory, backupEntry) : null;
50+};
51+
4252 const createLegacySchema = async (db: PGlite) => {
4353 await db.exec(`
4454 create table local_installations (
@@ -470,11 +480,20 @@ const seedInitialLegacyWorkspace = async (legacyLocalDataDir: string) => { · 425 unmodified lines
470480 }
471481 };
472482
473−const appendUnmigratedLegacySource = async (legacyLocalDataDir: string) => {
483+const seedFreshLegacyWorkspaceWithSecondSource = async (legacyLocalDataDir: string) => {
474484 const db = new PGlite(legacyLocalDataDir);
475485 await db.waitReady;
476486
477487 try {
488+ await createLegacySchema(db);
489+ await db.query(
490+ "insert into local_installations (id, account_id, workspace_id) values ($1, $2, $3)",
491+ [LEGACY_INSTALLATION_ID, LEGACY_ACCOUNT_ID, LEGACY_WORKSPACE_ID],
492+ );
493+ await db.query(
494+ "insert into workspaces (id, name) values ($1, $2)",
495+ [LEGACY_WORKSPACE_ID, "Replacement Legacy Workspace"],
496+ );
478497 await db.query(
479498 `insert into sources (
480499 workspace_id,
@@ -538,6 +557,7 @@ describe("legacy-postgres-migration", () => { · 57 unmodified lines
538557 homeStateDirectory,
539558 localDataDir: legacyLocalDataDir,
540559 });
560+ const legacyBackupPath = findLegacyBackupPath(legacyLocalDataDir);
541561
542562 const context = yield* resolveLocalWorkspaceContext({
543563 workspaceRoot,
@@ -555,6 +575,9 @@ describe("legacy-postgres-migration", () => { · 11 unmodified lines
555575 expect(existsSync(context.projectConfigPath)).toBe(true);
556576 expect(existsSync(localWorkspaceStatePath(context))).toBe(true);
557577 expect(existsSync(localControlPlaneStatePath(context))).toBe(true);
578+ expect(existsSync(legacyLocalDataDir)).toBe(false);
579+ expect(legacyBackupPath).not.toBeNull();
580+ expect(legacyBackupPath !== null ? existsSync(legacyBackupPath) : false).toBe(true);
558581
559582 expect(loadedConfig.projectConfig?.sources?.[LEGACY_SOURCE_ID]?.kind).toBe("mcp");
560583 expect(
@@ -616,7 +639,9 @@ describe("legacy-postgres-migration", () => { · 55 unmodified lines
616639
617640 yield* Effect.promise(() => firstRuntime.close());
618641
619− yield* Effect.promise(() => appendUnmigratedLegacySource(legacyLocalDataDir));
642+ yield* Effect.promise(() =>
643+ seedFreshLegacyWorkspaceWithSecondSource(legacyLocalDataDir),
644+ );
620645
621646 const secondRuntime = yield* createControlPlaneRuntime({
622647 workspaceRoot,
packages/control-plane/src/runtime/legacy-postgres-migration.ts+91−11
@@ -1,5 +1,5 @@
11 import { existsSync, readFileSync } from "node:fs";
2−import { mkdir, unlink } from "node:fs/promises";
2+import { mkdir, rename, unlink } from "node:fs/promises";
33 import { join, resolve } from "node:path";
44
55 import { FileSystem } from "@effect/platform";
@@ -243,6 +243,21 @@ type LegacyWorkspaceSnapshot = { · 237 unmodified lines
243243 toolArtifacts: LegacyToolArtifactRow[];
244244 };
245245
246+type LegacySnapshotSource =
247+ | {
248+ kind: "pglite";
249+ localDataDir: string;
250+ }
251+ | {
252+ kind: "postgres";
253+ databaseUrl: string;
254+ };
255+
256+type LoadedLegacyWorkspaceSnapshot = {
257+ snapshot: LegacyWorkspaceSnapshot;
258+ source: LegacySnapshotSource;
259+};
260+
246261 type LegacyQueryClient = {
247262 label: string;
248263 query: (sql: string) => Promise<LegacyRow[]>;
@@ -726,31 +741,71 @@ const loadLegacyWorkspaceSnapshot = async ( · 477 unmodified lines
726741 };
727742 };
728743
744+const backupLegacyPGliteDataDir = async (
745+ localDataDir: string,
746+): Promise<string | null> => {
747+ if (localDataDir.trim() === ":memory:") {
748+ return null;
749+ }
750+
751+ const resolvedDataDir = resolve(localDataDir);
752+ if (!existsSync(resolvedDataDir)) {
753+ return null;
754+ }
755+
756+ const backupPath = `${resolvedDataDir}.migrated-backup-${Date.now()}`;
757+ await rename(resolvedDataDir, backupPath);
758+ return backupPath;
759+};
760+
729761 const tryLoadLegacyWorkspaceSnapshot = async (input: {
730762 localDataDir?: string;
731763 databaseUrl?: string;
732−}): Promise<LegacyWorkspaceSnapshot | null> => {
733− const clients: LegacyQueryClient[] = [];
764+}): Promise<LoadedLegacyWorkspaceSnapshot | null> => {
765+ const clients: Array<{
766+ client: LegacyQueryClient;
767+ source: LegacySnapshotSource;
768+ }> = [];
734769
735770 try {
736771 if (trimOrNull(input.localDataDir) !== null) {
737772 const localClient = await openLegacyPGliteClient(input.localDataDir!);
738773 if (localClient !== null) {
739− clients.push(localClient);
774+ clients.push({
775+ client: localClient,
776+ source: {
777+ kind: "pglite",
778+ localDataDir: input.localDataDir!,
779+ },
780+ });
740781 }
741782 }
742783
743784 if (clients.length === 0 && isPostgresUrl(input.databaseUrl)) {
744− clients.push(openLegacyPostgresClient(input.databaseUrl!));
785+ clients.push({
786+ client: openLegacyPostgresClient(input.databaseUrl!),
787+ source: {
788+ kind: "postgres",
789+ databaseUrl: input.databaseUrl!,
790+ },
791+ });
745792 }
746793
747− for (const client of clients) {
794+ for (const { client, source } of clients) {
748795 try {
749796 if (!(await hasLegacySchema(client))) {
750797 continue;
751798 }
752799
753− return await loadLegacyWorkspaceSnapshot(client);
800+ const snapshot = await loadLegacyWorkspaceSnapshot(client);
801+ if (snapshot === null) {
802+ continue;
803+ }
804+
805+ return {
806+ snapshot,
807+ source,
808+ };
754809 } catch {
755810 continue;
756811 }
@@ -759,7 +814,7 @@ const tryLoadLegacyWorkspaceSnapshot = async (input: { · 2 unmodified lines
759814 return null;
760815 } finally {
761816 await Promise.all(
762− clients.map((client) =>
817+ clients.map(({ client }) =>
763818 client.close().catch(() => undefined)),
764819 );
765820 }
@@ -1526,7 +1581,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 760 unmodified lines
15261581 return;
15271582 }
15281583
1529− const snapshot = yield* Effect.tryPromise({
1584+ const loadedSnapshot = yield* Effect.tryPromise({
15301585 try: () =>
15311586 tryLoadLegacyWorkspaceSnapshot({
15321587 localDataDir: input.legacyLocalDataDir,
@@ -1534,7 +1589,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line
15341589 }),
15351590 catch: toError,
15361591 });
1537− if (snapshot === null) {
1592+ if (loadedSnapshot === null) {
15381593 return;
15391594 }
15401595
@@ -1542,7 +1597,7 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 1 unmodified line
15421597 try: () =>
15431598 migrateLegacyWorkspace({
15441599 context: input.context,
1545− snapshot,
1600+ snapshot: loadedSnapshot.snapshot,
15461601 }),
15471602 catch: toError,
15481603 });
@@ -1570,6 +1625,31 @@ export const migrateLegacyPostgresWorkspaceIfNeeded = (input: { · 21 unmodified lines
15701625 { discard: true },
15711626 );
15721627
1628+ if (loadedSnapshot.source.kind === "pglite") {
1629+ const legacyLocalDataDir = loadedSnapshot.source.localDataDir;
1630+ const backupExit = yield* Effect.exit(
1631+ Effect.tryPromise({
1632+ try: () => backupLegacyPGliteDataDir(legacyLocalDataDir),
1633+ catch: toError,
1634+ }),
1635+ );
1636+
1637+ yield* Effect.sync(() => {
1638+ if (backupExit._tag === "Success" && backupExit.value !== null) {
1639+ console.warn(
1640+ `[executor] Backed up legacy local data dir to: ${backupExit.value}`,
1641+ );
1642+ return;
1643+ }
1644+
1645+ if (backupExit._tag === "Failure") {
1646+ console.warn(
1647+ `[executor] Migrated workspace files but failed to back up legacy local data dir: ${String(backupExit.cause)}`,
1648+ );
1649+ }
1650+ });
1651+ }
1652+
15731653 yield* Effect.sync(() => {
15741654 console.warn(
15751655 `[executor] Migrated ${String(migrated.sourceCount)} legacy source(s) and ${String(migrated.policyCount)} policy/policies into ${input.context.projectConfigPath}`,

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t