Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Avoid emulator data loss when there an error during export (#3504)
9 changes: 0 additions & 9 deletions src/emulator/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
34 changes: 23 additions & 11 deletions src/emulator/hubExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<void> {
Expand All @@ -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,
};

Expand Down Expand Up @@ -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);
}
Expand All @@ -185,7 +197,7 @@ export class HubExport {
private async exportAuth(metadata: ExportMetadata): Promise<void> {
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);
}
Expand Down Expand Up @@ -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);
}
Expand Down