-
Notifications
You must be signed in to change notification settings - Fork 902
/
hubExport.ts
151 lines (129 loc) · 4.97 KB
/
hubExport.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import * as path from "path";
import * as fs from "fs";
import * as http from "http";
import * as api from "../api";
import * as logger from "../logger";
import { IMPORT_EXPORT_EMULATORS, Emulators, ALL_EMULATORS } from "./types";
import { EmulatorRegistry } from "./registry";
import { FirebaseError } from "../error";
import { EmulatorHub } from "./hub";
import { getDownloadDetails } from "./downloadableEmulators";
export interface ExportMetadata {
version: string;
firestore?: {
version: string;
path: string;
metadata_file: string;
};
database?: {
version: string;
path: string;
};
}
export class HubExport {
static METADATA_FILE_NAME = "firebase-export-metadata.json";
constructor(private projectId: string, private exportPath: string) {}
public static readMetadata(exportPath: string): ExportMetadata | undefined {
const metadataPath = path.join(exportPath, this.METADATA_FILE_NAME);
if (!fs.existsSync(metadataPath)) {
return undefined;
}
return JSON.parse(fs.readFileSync(metadataPath, "utf8").toString()) as ExportMetadata;
}
public async exportAll(): Promise<void> {
const toExport = ALL_EMULATORS.filter(this.shouldExport);
if (toExport.length === 0) {
throw new FirebaseError("No running emulators support import/export.");
}
// TODO(samstern): Once we add other emulators, we have to deal with the fact that
// there may be an existing metadata file and it may only partially overlap with
// the new one.
const metadata: ExportMetadata = {
version: EmulatorHub.CLI_VERSION,
};
if (this.shouldExport(Emulators.FIRESTORE)) {
metadata.firestore = {
version: getDownloadDetails(Emulators.FIRESTORE).version,
path: "firestore_export",
metadata_file: "firestore_export/firestore_export.overall_export_metadata",
};
await this.exportFirestore(metadata);
}
if (this.shouldExport(Emulators.DATABASE)) {
metadata.database = {
version: getDownloadDetails(Emulators.DATABASE).version,
path: "database_export",
};
await this.exportDatabase(metadata);
}
const metadataPath = path.join(this.exportPath, HubExport.METADATA_FILE_NAME);
fs.writeFileSync(metadataPath, JSON.stringify(metadata));
}
private async exportFirestore(metadata: ExportMetadata): Promise<void> {
const firestoreInfo = EmulatorRegistry.get(Emulators.FIRESTORE)!!.getInfo();
const firestoreHost = `http://${firestoreInfo.host}:${firestoreInfo.port}`;
const firestoreExportBody = {
database: `projects/${this.projectId}/databases/(default)`,
export_directory: this.exportPath,
export_name: metadata.firestore!!.path,
};
return api.request("POST", `/emulator/v1/projects/${this.projectId}:export`, {
origin: firestoreHost,
json: true,
data: firestoreExportBody,
});
}
private async exportDatabase(metadata: ExportMetadata): Promise<void> {
const databaseInfo = EmulatorRegistry.get(Emulators.DATABASE)!.getInfo();
const databaseAddr = `http://${databaseInfo.host}:${databaseInfo.port}`;
// Get the list of namespaces
const inspectURL = `/.inspect/databases.json?ns=${this.projectId}`;
const inspectRes = await api.request("GET", inspectURL, { origin: databaseAddr, auth: true });
const namespaces = inspectRes.body.map((instance: any) => instance.name);
// Check each one for actual data
const nonEmptyNamespaces = [];
for (const ns of namespaces) {
const checkDataPath = `/.json?ns=${ns}&shallow=true&limitToFirst=1`;
const checkDataRes = await api.request("GET", checkDataPath, {
origin: databaseAddr,
auth: true,
});
if (checkDataRes.body !== null) {
nonEmptyNamespaces.push(ns);
} else {
logger.debug(`Namespace ${ns} contained null data, not exporting`);
}
}
// Make sure the export directory exists
if (!fs.existsSync(this.exportPath)) {
fs.mkdirSync(this.exportPath);
}
const dbExportPath = path.join(this.exportPath, metadata.database!.path);
if (!fs.existsSync(dbExportPath)) {
fs.mkdirSync(dbExportPath);
}
for (const ns of nonEmptyNamespaces) {
const exportFile = path.join(dbExportPath, `${ns}.json`);
const writeStream = fs.createWriteStream(exportFile);
logger.debug(`Exporting database instance: ${ns} to ${exportFile}`);
await new Promise((resolve, reject) => {
http
.get(
{
host: databaseInfo.host,
port: databaseInfo.port,
path: `/.json?ns=${ns}&format=export`,
headers: { Authorization: "Bearer owner" },
},
(response) => {
response.pipe(writeStream, { end: true }).once("close", resolve);
}
)
.on("error", reject);
});
}
}
private shouldExport(e: Emulators): boolean {
return IMPORT_EXPORT_EMULATORS.indexOf(e) >= 0 && EmulatorRegistry.isRunning(e);
}
}