From 566bb5ad62c36fc0c36b62ef86fe9f55c53ed93c Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Jun 2021 16:36:42 +0100 Subject: [PATCH] Avoid emulator data loss when there an error during export --- CHANGELOG.md | 1 + src/emulator/controller.ts | 9 --------- src/emulator/hubExport.ts | 34 +++++++++++++++++++++++----------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..e9104a0280d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Avoid emulator data loss when there an error during export (#3504) diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 39f32215879..f4bb80cf785 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -774,15 +774,6 @@ export async function exportEmulatorData(exportPath: string, options: any) { } } - // Remove all existing data (metadata.json will be overwritten automatically) - if (existingMetadata) { - if (existingMetadata.firestore) { - const firestorePath = path.join(exportAbsPath, existingMetadata.firestore.path); - utils.logBullet(`Deleting directory ${firestorePath}`); - rimraf.sync(firestorePath); - } - } - utils.logBullet(`Exporting data to: ${exportAbsPath}`); try { await hubClient.postExport(exportAbsPath); diff --git a/src/emulator/hubExport.ts b/src/emulator/hubExport.ts index 4ba51b523ec..e6e117d52f0 100644 --- a/src/emulator/hubExport.ts +++ b/src/emulator/hubExport.ts @@ -12,6 +12,7 @@ import { EmulatorHub } from "./hub"; import { getDownloadDetails } from "./downloadableEmulators"; import { DatabaseEmulator } from "./databaseEmulator"; import { StorageEmulator } from "./storage"; +import * as rimraf from "rimraf"; export interface FirestoreExportMetadata { version: string; @@ -45,7 +46,11 @@ export interface ExportMetadata { export class HubExport { static METADATA_FILE_NAME = "firebase-export-metadata.json"; - constructor(private projectId: string, private exportPath: string) {} + private tmpDir: string; + + constructor(private projectId: string, private exportPath: string) { + this.tmpDir = fs.mkdtempSync(`firebase-export-${new Date().getTime()}`); + } public static readMetadata(exportPath: string): ExportMetadata | undefined { const metadataPath = path.join(exportPath, this.METADATA_FILE_NAME); @@ -102,8 +107,20 @@ export class HubExport { await this.exportStorage(metadata); } - const metadataPath = path.join(this.exportPath, HubExport.METADATA_FILE_NAME); + // Make sure the export directory exists + if (!fs.existsSync(this.exportPath)) { + fs.mkdirSync(this.exportPath); + } + + // Write the metadata file after everything else has succeeded + const metadataPath = path.join(this.tmpDir, HubExport.METADATA_FILE_NAME); fs.writeFileSync(metadataPath, JSON.stringify(metadata, undefined, 2)); + + // Remove any existing data in the directory and then swap it with the + // temp directory. + logger.debug(`hubExport: swapping ${this.tmpDir} with ${this.exportPath}`); + rimraf.sync(this.exportPath); + fse.moveSync(this.tmpDir, this.exportPath); } private async exportFirestore(metadata: ExportMetadata): Promise { @@ -112,7 +129,7 @@ export class HubExport { const firestoreExportBody = { database: `projects/${this.projectId}/databases/(default)`, - export_directory: this.exportPath, + export_directory: this.tmpDir, export_name: metadata.firestore!!.path, }; @@ -155,12 +172,7 @@ export class HubExport { } } - // Make sure the export directory exists - if (!fs.existsSync(this.exportPath)) { - fs.mkdirSync(this.exportPath); - } - - const dbExportPath = path.join(this.exportPath, metadata.database!.path); + const dbExportPath = path.join(this.tmpDir, metadata.database!.path); if (!fs.existsSync(dbExportPath)) { fs.mkdirSync(dbExportPath); } @@ -185,7 +197,7 @@ export class HubExport { private async exportAuth(metadata: ExportMetadata): Promise { const { host, port } = EmulatorRegistry.get(Emulators.AUTH)!.getInfo(); - const authExportPath = path.join(this.exportPath, metadata.auth!.path); + const authExportPath = path.join(this.tmpDir, metadata.auth!.path); if (!fs.existsSync(authExportPath)) { fs.mkdirSync(authExportPath); } @@ -221,7 +233,7 @@ export class HubExport { const storageEmulator = EmulatorRegistry.get(Emulators.STORAGE) as StorageEmulator; // Clear the export - const storageExportPath = path.join(this.exportPath, metadata.storage!.path); + const storageExportPath = path.join(this.tmpDir, metadata.storage!.path); if (fs.existsSync(storageExportPath)) { fse.removeSync(storageExportPath); }