No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hubExport.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.HubExport = void 0;
  4. const path = require("path");
  5. const fs = require("fs");
  6. const fse = require("fs-extra");
  7. const http = require("http");
  8. const logger_1 = require("../logger");
  9. const types_1 = require("./types");
  10. const registry_1 = require("./registry");
  11. const error_1 = require("../error");
  12. const hub_1 = require("./hub");
  13. const downloadableEmulators_1 = require("./downloadableEmulators");
  14. const rimraf = require("rimraf");
  15. const track_1 = require("../track");
  16. class HubExport {
  17. constructor(projectId, options) {
  18. this.projectId = projectId;
  19. this.options = options;
  20. this.exportPath = options.path;
  21. this.tmpDir = fs.mkdtempSync(`firebase-export-${new Date().getTime()}`);
  22. }
  23. static readMetadata(exportPath) {
  24. const metadataPath = path.join(exportPath, this.METADATA_FILE_NAME);
  25. if (!fs.existsSync(metadataPath)) {
  26. return undefined;
  27. }
  28. return JSON.parse(fs.readFileSync(metadataPath, "utf8").toString());
  29. }
  30. async exportAll() {
  31. const toExport = types_1.ALL_EMULATORS.filter(shouldExport);
  32. if (toExport.length === 0) {
  33. throw new error_1.FirebaseError("No running emulators support import/export.");
  34. }
  35. const metadata = {
  36. version: hub_1.EmulatorHub.CLI_VERSION,
  37. };
  38. if (shouldExport(types_1.Emulators.FIRESTORE)) {
  39. metadata.firestore = {
  40. version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.FIRESTORE).version,
  41. path: "firestore_export",
  42. metadata_file: "firestore_export/firestore_export.overall_export_metadata",
  43. };
  44. await this.exportFirestore(metadata);
  45. }
  46. if (shouldExport(types_1.Emulators.DATABASE)) {
  47. metadata.database = {
  48. version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.DATABASE).version,
  49. path: "database_export",
  50. };
  51. await this.exportDatabase(metadata);
  52. }
  53. if (shouldExport(types_1.Emulators.AUTH)) {
  54. metadata.auth = {
  55. version: hub_1.EmulatorHub.CLI_VERSION,
  56. path: "auth_export",
  57. };
  58. await this.exportAuth(metadata);
  59. }
  60. if (shouldExport(types_1.Emulators.STORAGE)) {
  61. metadata.storage = {
  62. version: hub_1.EmulatorHub.CLI_VERSION,
  63. path: "storage_export",
  64. };
  65. await this.exportStorage(metadata);
  66. }
  67. if (!fs.existsSync(this.exportPath)) {
  68. fs.mkdirSync(this.exportPath);
  69. }
  70. void (0, track_1.trackEmulator)("emulator_export", {
  71. initiated_by: this.options.initiatedBy,
  72. emulator_name: types_1.Emulators.HUB,
  73. });
  74. const metadataPath = path.join(this.tmpDir, HubExport.METADATA_FILE_NAME);
  75. fs.writeFileSync(metadataPath, JSON.stringify(metadata, undefined, 2));
  76. logger_1.logger.debug(`hubExport: swapping ${this.tmpDir} with ${this.exportPath}`);
  77. rimraf.sync(this.exportPath);
  78. fse.moveSync(this.tmpDir, this.exportPath);
  79. }
  80. async exportFirestore(metadata) {
  81. void (0, track_1.trackEmulator)("emulator_export", {
  82. initiated_by: this.options.initiatedBy,
  83. emulator_name: types_1.Emulators.FIRESTORE,
  84. });
  85. const firestoreExportBody = {
  86. database: `projects/${this.projectId}/databases/(default)`,
  87. export_directory: this.tmpDir,
  88. export_name: metadata.firestore.path,
  89. };
  90. await registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE).post(`/emulator/v1/projects/${this.projectId}:export`, firestoreExportBody);
  91. }
  92. async exportDatabase(metadata) {
  93. const databaseEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
  94. const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE, { auth: true });
  95. const inspectURL = `/.inspect/databases.json`;
  96. const inspectRes = await client.get(inspectURL, {
  97. queryParams: { ns: this.projectId },
  98. });
  99. const namespaces = inspectRes.body.map((instance) => instance.name);
  100. const namespacesToExport = [];
  101. for (const ns of namespaces) {
  102. const checkDataPath = `/.json`;
  103. const checkDataRes = await client.get(checkDataPath, {
  104. queryParams: {
  105. ns,
  106. shallow: "true",
  107. limitToFirst: 1,
  108. },
  109. });
  110. if (checkDataRes.body !== null) {
  111. namespacesToExport.push(ns);
  112. }
  113. else {
  114. logger_1.logger.debug(`Namespace ${ns} contained null data, not exporting`);
  115. }
  116. }
  117. for (const ns of databaseEmulator.getImportedNamespaces()) {
  118. if (!namespacesToExport.includes(ns)) {
  119. logger_1.logger.debug(`Namespace ${ns} was imported, exporting.`);
  120. namespacesToExport.push(ns);
  121. }
  122. }
  123. void (0, track_1.trackEmulator)("emulator_export", {
  124. initiated_by: this.options.initiatedBy,
  125. emulator_name: types_1.Emulators.DATABASE,
  126. count: namespacesToExport.length,
  127. });
  128. const dbExportPath = path.join(this.tmpDir, metadata.database.path);
  129. if (!fs.existsSync(dbExportPath)) {
  130. fs.mkdirSync(dbExportPath);
  131. }
  132. const { host, port } = databaseEmulator.getInfo();
  133. for (const ns of namespacesToExport) {
  134. const exportFile = path.join(dbExportPath, `${ns}.json`);
  135. logger_1.logger.debug(`Exporting database instance: ${ns} to ${exportFile}`);
  136. await fetchToFile({
  137. host,
  138. port,
  139. path: `/.json?ns=${ns}&format=export`,
  140. headers: { Authorization: "Bearer owner" },
  141. }, exportFile);
  142. }
  143. }
  144. async exportAuth(metadata) {
  145. void (0, track_1.trackEmulator)("emulator_export", {
  146. initiated_by: this.options.initiatedBy,
  147. emulator_name: types_1.Emulators.AUTH,
  148. });
  149. const { host, port } = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH).getInfo();
  150. const authExportPath = path.join(this.tmpDir, metadata.auth.path);
  151. if (!fs.existsSync(authExportPath)) {
  152. fs.mkdirSync(authExportPath);
  153. }
  154. const accountsFile = path.join(authExportPath, "accounts.json");
  155. logger_1.logger.debug(`Exporting auth users in Project ${this.projectId} to ${accountsFile}`);
  156. await fetchToFile({
  157. host,
  158. port,
  159. path: `/identitytoolkit.googleapis.com/v1/projects/${this.projectId}/accounts:batchGet?maxResults=-1`,
  160. headers: { Authorization: "Bearer owner" },
  161. }, accountsFile);
  162. const configFile = path.join(authExportPath, "config.json");
  163. logger_1.logger.debug(`Exporting project config in Project ${this.projectId} to ${accountsFile}`);
  164. await fetchToFile({
  165. host,
  166. port,
  167. path: `/emulator/v1/projects/${this.projectId}/config`,
  168. headers: { Authorization: "Bearer owner" },
  169. }, configFile);
  170. }
  171. async exportStorage(metadata) {
  172. const storageExportPath = path.join(this.tmpDir, metadata.storage.path);
  173. if (fs.existsSync(storageExportPath)) {
  174. fse.removeSync(storageExportPath);
  175. }
  176. fs.mkdirSync(storageExportPath, { recursive: true });
  177. const storageExportBody = {
  178. path: storageExportPath,
  179. initiatedBy: this.options.initiatedBy,
  180. };
  181. const res = await registry_1.EmulatorRegistry.client(types_1.Emulators.STORAGE).request({
  182. method: "POST",
  183. path: "/internal/export",
  184. headers: { "Content-Type": "application/json" },
  185. body: storageExportBody,
  186. responseType: "stream",
  187. resolveOnHTTPError: true,
  188. });
  189. if (res.status >= 400) {
  190. throw new error_1.FirebaseError(`Failed to export storage: ${await res.response.text()}`);
  191. }
  192. }
  193. }
  194. exports.HubExport = HubExport;
  195. HubExport.METADATA_FILE_NAME = "firebase-export-metadata.json";
  196. function fetchToFile(options, path) {
  197. const writeStream = fs.createWriteStream(path);
  198. return new Promise((resolve, reject) => {
  199. http
  200. .get(options, (response) => {
  201. response.pipe(writeStream, { end: true }).once("close", resolve);
  202. })
  203. .on("error", reject);
  204. });
  205. }
  206. function shouldExport(e) {
  207. return types_1.IMPORT_EXPORT_EMULATORS.includes(e) && registry_1.EmulatorRegistry.isRunning(e);
  208. }