firebase-tools 14.12.1 → 14.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/commands/dataconnect-services-list.js +5 -5
- package/lib/commands/dataconnect-sql-grant.js +5 -0
- package/lib/commands/dataconnect-sql-setup.js +1 -3
- package/lib/crashlytics/getIssueDetails.js +41 -0
- package/lib/crashlytics/getSampleCrash.js +48 -0
- package/lib/dataconnect/client.js +23 -15
- package/lib/dataconnect/ensureApis.js +5 -9
- package/lib/dataconnect/errors.js +7 -1
- package/lib/dataconnect/fileUtils.js +5 -6
- package/lib/dataconnect/freeTrial.js +16 -39
- package/lib/dataconnect/provisionCloudSql.js +67 -70
- package/lib/dataconnect/schemaMigration.js +222 -170
- package/lib/deploy/dataconnect/deploy.js +9 -11
- package/lib/deploy/dataconnect/prepare.js +7 -10
- package/lib/deploy/dataconnect/release.js +42 -30
- package/lib/deploy/functions/backend.js +8 -2
- package/lib/deploy/functions/build.js +23 -1
- package/lib/deploy/functions/ensure.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -1
- package/lib/deploy/functions/prepare.js +8 -4
- package/lib/deploy/functions/pricing.js +12 -5
- package/lib/deploy/functions/release/fabricator.js +25 -3
- package/lib/emulator/controller.js +7 -3
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/functionsEmulator.js +11 -1
- package/lib/experiments.js +4 -0
- package/lib/extensions/extensionsHelper.js +4 -15
- package/lib/extensions/utils.js +1 -12
- package/lib/firestore/api.js +25 -11
- package/lib/firestore/pretty-print.js +7 -0
- package/lib/functional.js +7 -1
- package/lib/functions/env.js +19 -15
- package/lib/functions/projectConfig.js +25 -2
- package/lib/functions/secrets.js +3 -0
- package/lib/gcp/cloudfunctionsv2.js +3 -31
- package/lib/gcp/cloudscheduler.js +1 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
- package/lib/gcp/cloudsql/connect.js +3 -2
- package/lib/gcp/cloudsql/permissionsSetup.js +23 -16
- package/lib/gcp/k8s.js +32 -0
- package/lib/gcp/runv2.js +178 -0
- package/lib/gemini/fdcExperience.js +5 -3
- package/lib/init/features/dataconnect/index.js +266 -162
- package/lib/init/features/dataconnect/sdk.js +36 -20
- package/lib/init/features/project.js +4 -0
- package/lib/management/studio.js +1 -1
- package/lib/mcp/tools/core/init.js +7 -6
- package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
- package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
- package/lib/mcp/tools/crashlytics/index.js +7 -1
- package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
- package/lib/rtdb.js +1 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +6 -0
- package/lib/extensions/resolveSource.js +0 -24
|
@@ -16,97 +16,121 @@ const utils_1 = require("../utils");
|
|
|
16
16
|
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
17
17
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
18
18
|
const errors = require("./errors");
|
|
19
|
+
const provisionCloudSql_1 = require("./provisionCloudSql");
|
|
20
|
+
const requireAuth_1 = require("../requireAuth");
|
|
19
21
|
async function setupSchemaIfNecessary(instanceId, databaseId, options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
schemaInfo.setupStatus
|
|
24
|
-
|
|
22
|
+
try {
|
|
23
|
+
await (0, connect_1.setupIAMUsers)(instanceId, options);
|
|
24
|
+
const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
|
|
25
|
+
switch (schemaInfo.setupStatus) {
|
|
26
|
+
case permissionsSetup_1.SchemaSetupStatus.BrownField:
|
|
27
|
+
case permissionsSetup_1.SchemaSetupStatus.GreenField:
|
|
28
|
+
logger_1.logger.debug(`Cloud SQL Database ${instanceId}:${databaseId} is already set up in ${schemaInfo.setupStatus}`);
|
|
29
|
+
return schemaInfo.setupStatus;
|
|
30
|
+
case permissionsSetup_1.SchemaSetupStatus.NotSetup:
|
|
31
|
+
case permissionsSetup_1.SchemaSetupStatus.NotFound:
|
|
32
|
+
(0, utils_1.logLabeledBullet)("dataconnect", "Setting up Cloud SQL Database SQL permissions...");
|
|
33
|
+
return await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options, true);
|
|
34
|
+
default:
|
|
35
|
+
throw new error_1.FirebaseError(`Unexpected schema setup status: ${schemaInfo.setupStatus}`);
|
|
36
|
+
}
|
|
25
37
|
}
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw new error_1.FirebaseError(`Cannot setup Postgres SQL permissions of Cloud SQL database ${instanceId}:${databaseId}\n${err}`);
|
|
28
40
|
}
|
|
29
|
-
return schemaInfo.setupStatus;
|
|
30
41
|
}
|
|
31
42
|
async function diffSchema(options, schema, schemaValidation) {
|
|
32
|
-
|
|
43
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "STRICT";
|
|
44
|
+
setSchemaValidationMode(schema, validationMode);
|
|
45
|
+
displayStartSchemaDiff(validationMode);
|
|
33
46
|
const { serviceName, instanceName, databaseId, instanceId } = getIdentifiers(schema);
|
|
34
47
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
35
|
-
let
|
|
36
|
-
await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
37
|
-
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
38
|
-
setSchemaValidationMode(schema, validationMode);
|
|
48
|
+
let incompatible = undefined;
|
|
39
49
|
try {
|
|
40
50
|
await (0, client_1.upsertSchema)(schema, true);
|
|
41
|
-
|
|
42
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is compatible.`);
|
|
46
|
-
}
|
|
51
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
47
52
|
}
|
|
48
53
|
catch (err) {
|
|
49
54
|
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
50
55
|
throw err;
|
|
51
56
|
}
|
|
57
|
+
incompatible = errors.getIncompatibleSchemaError(err);
|
|
52
58
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
53
|
-
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
54
59
|
if (!incompatible && !invalidConnectors.length) {
|
|
60
|
+
const gqlErrs = errors.getGQLErrors(err);
|
|
61
|
+
if (gqlErrs) {
|
|
62
|
+
throw new error_1.FirebaseError(`There are errors in your schema files:\n${gqlErrs}`);
|
|
63
|
+
}
|
|
55
64
|
throw err;
|
|
56
65
|
}
|
|
57
66
|
if (invalidConnectors.length) {
|
|
58
67
|
displayInvalidConnectors(invalidConnectors);
|
|
59
68
|
}
|
|
60
|
-
if (incompatible) {
|
|
61
|
-
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
62
|
-
diffs = incompatible.diffs;
|
|
63
|
-
}
|
|
64
69
|
}
|
|
65
|
-
if (!
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
85
|
-
}
|
|
86
|
-
diffs = incompatible.diffs;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
70
|
+
if (!incompatible) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
if (schemaValidation) {
|
|
74
|
+
displaySchemaChanges(incompatible, validationMode);
|
|
75
|
+
return incompatible.diffs;
|
|
76
|
+
}
|
|
77
|
+
const strictIncompatible = incompatible;
|
|
78
|
+
let compatibleIncompatible = undefined;
|
|
79
|
+
validationMode = "COMPATIBLE";
|
|
80
|
+
setSchemaValidationMode(schema, validationMode);
|
|
81
|
+
try {
|
|
82
|
+
displayStartSchemaDiff(validationMode);
|
|
83
|
+
await (0, client_1.upsertSchema)(schema, true);
|
|
84
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
88
|
+
throw err;
|
|
92
89
|
}
|
|
90
|
+
compatibleIncompatible = errors.getIncompatibleSchemaError(err);
|
|
93
91
|
}
|
|
94
|
-
|
|
92
|
+
if (!compatibleIncompatible) {
|
|
93
|
+
displaySchemaChanges(strictIncompatible, "STRICT");
|
|
94
|
+
}
|
|
95
|
+
else if (diffsEqual(strictIncompatible.diffs, compatibleIncompatible.diffs)) {
|
|
96
|
+
displaySchemaChanges(strictIncompatible, "STRICT");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
displaySchemaChanges(compatibleIncompatible, "COMPATIBLE");
|
|
100
|
+
displaySchemaChanges(strictIncompatible, "STRICT_AFTER_COMPATIBLE");
|
|
101
|
+
}
|
|
102
|
+
return incompatible.diffs;
|
|
95
103
|
}
|
|
96
104
|
exports.diffSchema = diffSchema;
|
|
97
105
|
async function migrateSchema(args) {
|
|
98
|
-
|
|
106
|
+
var _a;
|
|
99
107
|
const { options, schema, validateOnly, schemaValidation } = args;
|
|
108
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
109
|
+
setSchemaValidationMode(schema, validationMode);
|
|
110
|
+
displayStartSchemaDiff(validationMode);
|
|
111
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
100
112
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
101
113
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
102
|
-
await (
|
|
103
|
-
|
|
114
|
+
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
115
|
+
if (existingInstance.state === "PENDING_CREATE") {
|
|
116
|
+
const postgresql = (_a = schema.datasources.find((d) => d.postgresql)) === null || _a === void 0 ? void 0 : _a.postgresql;
|
|
117
|
+
if (!postgresql) {
|
|
118
|
+
throw new error_1.FirebaseError(`Cannot find Postgres datasource in the schema to deploy: ${serviceName}/schemas/${types_1.SCHEMA_ID}.\nIts datasources: ${JSON.stringify(schema.datasources)}`);
|
|
119
|
+
}
|
|
120
|
+
postgresql.schemaValidation = "NONE";
|
|
121
|
+
postgresql.schemaMigration = undefined;
|
|
122
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
123
|
+
postgresql.schemaValidation = undefined;
|
|
124
|
+
postgresql.schemaMigration = "MIGRATE_COMPATIBLE";
|
|
125
|
+
await (0, client_1.upsertSchema)(schema, validateOnly, true);
|
|
126
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Skip SQL schema migration because Cloud SQL is still being created`);
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
104
129
|
await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
105
|
-
let
|
|
106
|
-
setSchemaValidationMode(schema, validationMode);
|
|
130
|
+
let diffs = [];
|
|
107
131
|
try {
|
|
108
132
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
109
|
-
(
|
|
133
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
110
134
|
}
|
|
111
135
|
catch (err) {
|
|
112
136
|
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
@@ -115,9 +139,13 @@ async function migrateSchema(args) {
|
|
|
115
139
|
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
116
140
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
117
141
|
if (!incompatible && !invalidConnectors.length) {
|
|
142
|
+
const gqlErrs = errors.getGQLErrors(err);
|
|
143
|
+
if (gqlErrs) {
|
|
144
|
+
throw new error_1.FirebaseError(`There are errors in your schema files:\n${gqlErrs}`);
|
|
145
|
+
}
|
|
118
146
|
throw err;
|
|
119
147
|
}
|
|
120
|
-
const migrationMode = await promptForSchemaMigration(options,
|
|
148
|
+
const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, validationMode);
|
|
121
149
|
const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
|
|
122
150
|
if (incompatible) {
|
|
123
151
|
diffs = await handleIncompatibleSchemaError({
|
|
@@ -150,7 +178,7 @@ async function migrateSchema(args) {
|
|
|
150
178
|
if (!incompatible && !invalidConnectors.length) {
|
|
151
179
|
throw err;
|
|
152
180
|
}
|
|
153
|
-
const migrationMode = await promptForSchemaMigration(options,
|
|
181
|
+
const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, "STRICT_AFTER_COMPATIBLE");
|
|
154
182
|
if (incompatible) {
|
|
155
183
|
const maybeDiffs = await handleIncompatibleSchemaError({
|
|
156
184
|
options,
|
|
@@ -169,22 +197,13 @@ exports.migrateSchema = migrateSchema;
|
|
|
169
197
|
async function grantRoleToUserInSchema(options, schema) {
|
|
170
198
|
const role = options.role;
|
|
171
199
|
const email = options.email;
|
|
172
|
-
const { instanceId, databaseId } = getIdentifiers(schema);
|
|
173
|
-
|
|
174
|
-
const { user, mode } = (0, connect_1.toDatabaseUser)(email);
|
|
175
|
-
const fdcSqlRole = permissionsSetup_1.fdcSqlRoleMap[role](databaseId);
|
|
176
|
-
await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
|
|
177
|
-
const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
|
|
178
|
-
if (!userIsCSQLAdmin) {
|
|
179
|
-
throw new error_1.FirebaseError(`Only users with 'roles/cloudsql.admin' can grant SQL roles. If you do not have this role, ask your database administrator to run this command or manually grant ${fdcSqlRole} to ${user}`);
|
|
180
|
-
}
|
|
200
|
+
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
201
|
+
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
181
202
|
const schemaSetupStatus = await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
182
|
-
if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField &&
|
|
183
|
-
|
|
184
|
-
throw new error_1.FirebaseError(`Owner rule isn't available in brownfield databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
203
|
+
if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField && role === "owner") {
|
|
204
|
+
throw new error_1.FirebaseError(`Owner rule isn't available in ${schemaSetupStatus} databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
185
205
|
}
|
|
186
|
-
await
|
|
187
|
-
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
|
|
206
|
+
await (0, permissionsSetup_1.grantRoleTo)(options, instanceId, databaseId, role, email);
|
|
188
207
|
}
|
|
189
208
|
exports.grantRoleToUserInSchema = grantRoleToUserInSchema;
|
|
190
209
|
function diffsEqual(x, y) {
|
|
@@ -207,15 +226,15 @@ function setSchemaValidationMode(schema, schemaValidation) {
|
|
|
207
226
|
}
|
|
208
227
|
}
|
|
209
228
|
function getIdentifiers(schema) {
|
|
210
|
-
var _a, _b;
|
|
229
|
+
var _a, _b, _c;
|
|
211
230
|
const postgresDatasource = schema.datasources.find((d) => d.postgresql);
|
|
212
231
|
const databaseId = (_a = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
|
|
213
232
|
if (!databaseId) {
|
|
214
|
-
throw new error_1.FirebaseError("
|
|
233
|
+
throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a database name.");
|
|
215
234
|
}
|
|
216
|
-
const instanceName = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance;
|
|
235
|
+
const instanceName = (_c = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql) === null || _c === void 0 ? void 0 : _c.instance;
|
|
217
236
|
if (!instanceName) {
|
|
218
|
-
throw new error_1.FirebaseError("
|
|
237
|
+
throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a CloudSQL instance.");
|
|
219
238
|
}
|
|
220
239
|
const instanceId = instanceName.split("/").pop();
|
|
221
240
|
const serviceName = schema.name.replace(`/schemas/${types_1.SCHEMA_ID}`, "");
|
|
@@ -235,11 +254,7 @@ function suggestedCommand(serviceName, invalidConnectorNames) {
|
|
|
235
254
|
}
|
|
236
255
|
async function handleIncompatibleSchemaError(args) {
|
|
237
256
|
const { incompatibleSchemaError, options, instanceId, databaseId, choice } = args;
|
|
238
|
-
|
|
239
|
-
throw new error_1.FirebaseError("This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
|
|
240
|
-
}
|
|
241
|
-
const commandsToExecute = incompatibleSchemaError.diffs
|
|
242
|
-
.filter((d) => {
|
|
257
|
+
const commandsToExecute = incompatibleSchemaError.diffs.filter((d) => {
|
|
243
258
|
switch (choice) {
|
|
244
259
|
case "all":
|
|
245
260
|
return true;
|
|
@@ -248,16 +263,15 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
248
263
|
case "none":
|
|
249
264
|
return false;
|
|
250
265
|
}
|
|
251
|
-
})
|
|
252
|
-
.map((d) => d.sql);
|
|
266
|
+
});
|
|
253
267
|
if (commandsToExecute.length) {
|
|
254
|
-
const commandsToExecuteBySuperUser = commandsToExecute.filter(
|
|
255
|
-
const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !
|
|
268
|
+
const commandsToExecuteBySuperUser = commandsToExecute.filter(requireSuperUser);
|
|
269
|
+
const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !requireSuperUser(sql));
|
|
256
270
|
const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
|
|
257
271
|
if (!userIsCSQLAdmin && commandsToExecuteBySuperUser.length) {
|
|
258
272
|
throw new error_1.FirebaseError(`Some SQL commands required for this migration require Admin permissions.\n
|
|
259
273
|
Please ask a user with 'roles/cloudsql.admin' to apply the following commands.\n
|
|
260
|
-
${commandsToExecuteBySuperUser
|
|
274
|
+
${diffsToString(commandsToExecuteBySuperUser)}`);
|
|
261
275
|
}
|
|
262
276
|
const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
|
|
263
277
|
if (schemaInfo.setupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField) {
|
|
@@ -266,56 +280,67 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
266
280
|
`If you would like Data Connect to manage your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
267
281
|
}
|
|
268
282
|
if (!(await (0, permissionsSetup_1.checkSQLRoleIsGranted)(options, instanceId, databaseId, (0, permissions_1.firebaseowner)(databaseId), (await (0, connect_1.getIAMUser)(options)).user))) {
|
|
269
|
-
|
|
283
|
+
if (!userIsCSQLAdmin) {
|
|
284
|
+
throw new error_1.FirebaseError(`Command aborted. Only users granted firebaseowner SQL role can run migrations.`);
|
|
285
|
+
}
|
|
286
|
+
const account = (await (0, requireAuth_1.requireAuth)(options));
|
|
287
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Granting firebaseowner role to myself ${account}...`);
|
|
288
|
+
await (0, permissionsSetup_1.grantRoleTo)(options, instanceId, databaseId, "owner", account);
|
|
270
289
|
}
|
|
271
290
|
if (commandsToExecuteBySuperUser.length) {
|
|
272
|
-
|
|
273
|
-
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser, false);
|
|
291
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Executing admin SQL commands as superuser...`);
|
|
292
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser.map((d) => d.sql), false);
|
|
274
293
|
}
|
|
275
294
|
if (commandsToExecuteByOwner.length) {
|
|
276
|
-
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner], false);
|
|
295
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner.map((d) => d.sql)], false);
|
|
277
296
|
return incompatibleSchemaError.diffs;
|
|
278
297
|
}
|
|
279
298
|
}
|
|
280
299
|
return [];
|
|
281
300
|
}
|
|
282
|
-
async function promptForSchemaMigration(options,
|
|
301
|
+
async function promptForSchemaMigration(options, instanceId, databaseId, err, validateOnly, validationMode) {
|
|
283
302
|
if (!err) {
|
|
284
303
|
return "none";
|
|
285
304
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
displaySchemaChanges(err, validationMode, instanceName, databaseId);
|
|
305
|
+
const defaultChoice = validationMode === "STRICT_AFTER_COMPATIBLE" ? "none" : "all";
|
|
306
|
+
displaySchemaChanges(err, validationMode);
|
|
290
307
|
if (!options.nonInteractive) {
|
|
291
308
|
if (validateOnly && options.force) {
|
|
292
|
-
return
|
|
309
|
+
return defaultChoice;
|
|
310
|
+
}
|
|
311
|
+
let choices = [
|
|
312
|
+
{ name: "Execute all", value: "all" },
|
|
313
|
+
];
|
|
314
|
+
if (err.destructive) {
|
|
315
|
+
choices = [{ name: `Execute all ${clc.red("(including destructive)")}`, value: "all" }];
|
|
316
|
+
if (err.diffs.some((d) => !d.destructive)) {
|
|
317
|
+
choices.push({ name: "Execute safe only", value: "safe" });
|
|
318
|
+
}
|
|
293
319
|
}
|
|
294
|
-
const message = validationMode === "STRICT_AFTER_COMPATIBLE"
|
|
295
|
-
? `Would you like to execute these optional changes against ${databaseId} in your CloudSQL instance ${instanceName}?`
|
|
296
|
-
: `Would you like to execute these changes against ${databaseId} in your CloudSQL instance ${instanceName}?`;
|
|
297
|
-
let executeChangePrompt = "Execute changes";
|
|
298
320
|
if (validationMode === "STRICT_AFTER_COMPATIBLE") {
|
|
299
|
-
|
|
321
|
+
choices.push({ name: "Skip them", value: "none" });
|
|
300
322
|
}
|
|
301
|
-
|
|
302
|
-
|
|
323
|
+
else {
|
|
324
|
+
choices.push({ name: "Abort", value: "abort" });
|
|
303
325
|
}
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
const ans = await (0, prompt_1.select)({
|
|
327
|
+
message: `Do you want to execute these SQL against ${instanceId}:${databaseId}?`,
|
|
328
|
+
choices: choices,
|
|
329
|
+
default: defaultChoice,
|
|
330
|
+
});
|
|
331
|
+
if (ans === "abort") {
|
|
332
|
+
throw new error_1.FirebaseError("Command aborted.");
|
|
333
|
+
}
|
|
334
|
+
return ans;
|
|
310
335
|
}
|
|
311
336
|
if (!validateOnly) {
|
|
312
337
|
throw new error_1.FirebaseError("Command aborted. Your database schema is incompatible with your Data Connect schema. Run `firebase dataconnect:sql:migrate` to migrate your database schema");
|
|
313
338
|
}
|
|
314
339
|
else if (options.force) {
|
|
315
|
-
return
|
|
340
|
+
return defaultChoice;
|
|
316
341
|
}
|
|
317
342
|
else if (!err.destructive) {
|
|
318
|
-
return
|
|
343
|
+
return defaultChoice;
|
|
319
344
|
}
|
|
320
345
|
else {
|
|
321
346
|
throw new error_1.FirebaseError("Command aborted. This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
|
|
@@ -344,17 +369,27 @@ async function deleteInvalidConnectors(invalidConnectors) {
|
|
|
344
369
|
}
|
|
345
370
|
function displayInvalidConnectors(invalidConnectors) {
|
|
346
371
|
const connectorIds = invalidConnectors.map((i) => i.split("/").pop()).join(", ");
|
|
347
|
-
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
|
|
372
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${clc.bold(connectorIds)}.`);
|
|
348
373
|
(0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
|
|
349
374
|
}
|
|
350
|
-
async function ensureServiceIsConnectedToCloudSql(serviceName,
|
|
375
|
+
async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, linkIfNotConnected) {
|
|
376
|
+
var _a, _b, _c, _d;
|
|
351
377
|
let currentSchema = await (0, client_1.getSchema)(serviceName);
|
|
352
|
-
|
|
378
|
+
let postgresql = (_b = (_a = currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.datasources) === null || _a === void 0 ? void 0 : _a.find((d) => d.postgresql)) === null || _b === void 0 ? void 0 : _b.postgresql;
|
|
379
|
+
if ((currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.reconciling) &&
|
|
380
|
+
(postgresql === null || postgresql === void 0 ? void 0 : postgresql.ephemeral) &&
|
|
381
|
+
((_c = postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql) === null || _c === void 0 ? void 0 : _c.instance) &&
|
|
382
|
+
(postgresql === null || postgresql === void 0 ? void 0 : postgresql.schemaValidation) === "NONE") {
|
|
383
|
+
const [, , , , , serviceId] = serviceName.split("/");
|
|
384
|
+
const [, projectId, , , , instanceId] = postgresql.cloudSql.instance.split("/");
|
|
385
|
+
throw new error_1.FirebaseError(`While checking the service ${serviceId}, ` + (0, provisionCloudSql_1.cloudSQLBeingCreated)(projectId, instanceId));
|
|
386
|
+
}
|
|
387
|
+
if (!currentSchema || !postgresql) {
|
|
353
388
|
if (!linkIfNotConnected) {
|
|
354
389
|
(0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
|
|
355
390
|
return;
|
|
356
391
|
}
|
|
357
|
-
(0, utils_1.logLabeledBullet)("dataconnect", `
|
|
392
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
|
|
358
393
|
currentSchema = {
|
|
359
394
|
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
360
395
|
source: {
|
|
@@ -362,84 +397,95 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
362
397
|
},
|
|
363
398
|
datasources: [
|
|
364
399
|
{
|
|
365
|
-
postgresql: {
|
|
366
|
-
database: databaseId,
|
|
367
|
-
schemaValidation: "NONE",
|
|
368
|
-
cloudSql: {
|
|
369
|
-
instance: instanceId,
|
|
370
|
-
},
|
|
371
|
-
},
|
|
400
|
+
postgresql: { ephemeral: true },
|
|
372
401
|
},
|
|
373
402
|
],
|
|
374
403
|
};
|
|
375
404
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
405
|
+
if (!postgresql) {
|
|
406
|
+
postgresql = currentSchema.datasources[0].postgresql;
|
|
407
|
+
}
|
|
408
|
+
let alreadyConnected = !postgresql.ephemeral || false;
|
|
409
|
+
if (((_d = postgresql.cloudSql) === null || _d === void 0 ? void 0 : _d.instance) && postgresql.cloudSql.instance !== instanceName) {
|
|
410
|
+
alreadyConnected = false;
|
|
411
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\n From ${postgresql.cloudSql.instance}\n To ${instanceName}`);
|
|
380
412
|
}
|
|
381
|
-
if (
|
|
382
|
-
|
|
413
|
+
if (postgresql.database && postgresql.database !== databaseId) {
|
|
414
|
+
alreadyConnected = false;
|
|
415
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql.database} to ${databaseId}`);
|
|
383
416
|
}
|
|
384
|
-
if (
|
|
417
|
+
if (alreadyConnected) {
|
|
385
418
|
return;
|
|
386
419
|
}
|
|
387
|
-
postgresql.schemaValidation = "STRICT";
|
|
388
420
|
try {
|
|
421
|
+
postgresql.schemaValidation = "STRICT";
|
|
422
|
+
postgresql.database = databaseId;
|
|
423
|
+
postgresql.cloudSql = { instance: instanceName };
|
|
389
424
|
await (0, client_1.upsertSchema)(currentSchema, false);
|
|
390
425
|
}
|
|
391
426
|
catch (err) {
|
|
392
427
|
if ((err === null || err === void 0 ? void 0 : err.status) >= 500) {
|
|
393
428
|
throw err;
|
|
394
429
|
}
|
|
395
|
-
logger_1.logger.debug(err);
|
|
430
|
+
logger_1.logger.debug(`Failed to ensure service is connected to Cloud SQL: ${err.message}`);
|
|
396
431
|
}
|
|
397
432
|
}
|
|
398
433
|
exports.ensureServiceIsConnectedToCloudSql = ensureServiceIsConnectedToCloudSql;
|
|
399
|
-
function
|
|
434
|
+
function displayStartSchemaDiff(validationMode) {
|
|
435
|
+
switch (validationMode) {
|
|
436
|
+
case "COMPATIBLE":
|
|
437
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations to be compatible...`);
|
|
438
|
+
break;
|
|
439
|
+
case "STRICT":
|
|
440
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations to match exactly...`);
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function displayNoSchemaDiff(instanceId, databaseId, validationMode) {
|
|
445
|
+
switch (validationMode) {
|
|
446
|
+
case "COMPATIBLE":
|
|
447
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema of ${instanceId}:${databaseId} is compatible with Data Connect Schema.`);
|
|
448
|
+
break;
|
|
449
|
+
case "STRICT":
|
|
450
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema of ${instanceId}:${databaseId} matches Data Connect Schema exactly.`);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function displaySchemaChanges(error, validationMode) {
|
|
400
455
|
switch (error.violationType) {
|
|
401
456
|
case "INCOMPATIBLE_SCHEMA":
|
|
402
457
|
{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
message =
|
|
426
|
-
"Your PostgreSQL database " +
|
|
427
|
-
databaseId +
|
|
428
|
-
" in your CloudSQL instance " +
|
|
429
|
-
instanceName +
|
|
430
|
-
" must be migrated in order to match your application schema. " +
|
|
431
|
-
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
432
|
-
error.diffs.map(toString).join("\n");
|
|
458
|
+
switch (validationMode) {
|
|
459
|
+
case "COMPATIBLE":
|
|
460
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `PostgreSQL schema is incompatible with the Data Connect Schema.
|
|
461
|
+
Those SQL statements will migrate it to be compatible:
|
|
462
|
+
|
|
463
|
+
${diffsToString(error.diffs)}
|
|
464
|
+
`);
|
|
465
|
+
break;
|
|
466
|
+
case "STRICT_AFTER_COMPATIBLE":
|
|
467
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `PostgreSQL schema contains unused SQL objects not part of the Data Connect Schema.
|
|
468
|
+
Those SQL statements will migrate it to match exactly:
|
|
469
|
+
|
|
470
|
+
${diffsToString(error.diffs)}
|
|
471
|
+
`);
|
|
472
|
+
break;
|
|
473
|
+
case "STRICT":
|
|
474
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `PostgreSQL schema does not match the Data Connect Schema.
|
|
475
|
+
Those SQL statements will migrate it to match exactly:
|
|
476
|
+
|
|
477
|
+
${diffsToString(error.diffs)}
|
|
478
|
+
`);
|
|
479
|
+
break;
|
|
433
480
|
}
|
|
434
|
-
(0, utils_1.logLabeledWarning)("dataconnect", message);
|
|
435
481
|
}
|
|
436
482
|
break;
|
|
437
483
|
case "INACCESSIBLE_SCHEMA":
|
|
438
484
|
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
485
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Cannot access CloudSQL database to validate schema.
|
|
486
|
+
Here is the complete expected SQL schema:
|
|
487
|
+
${diffsToString(error.diffs)}
|
|
488
|
+
`);
|
|
443
489
|
(0, utils_1.logLabeledWarning)("dataconnect", "Some SQL resources may already exist.");
|
|
444
490
|
}
|
|
445
491
|
break;
|
|
@@ -447,6 +493,12 @@ function displaySchemaChanges(error, validationMode, instanceName, databaseId) {
|
|
|
447
493
|
throw new error_1.FirebaseError(`Unknown schema violation type: ${error.violationType}, IncompatibleSqlSchemaError: ${error}`);
|
|
448
494
|
}
|
|
449
495
|
}
|
|
450
|
-
function
|
|
496
|
+
function requireSuperUser(diff) {
|
|
497
|
+
return diff.sql.startsWith("CREATE EXTENSION") || diff.sql.startsWith("CREATE SCHEMA");
|
|
498
|
+
}
|
|
499
|
+
function diffsToString(diffs) {
|
|
500
|
+
return diffs.map(diffToString).join("\n\n");
|
|
501
|
+
}
|
|
502
|
+
function diffToString(diff) {
|
|
451
503
|
return `\/** ${diff.destructive ? clc.red("Destructive: ") : ""}${diff.description}*\/\n${(0, sql_formatter_1.format)(diff.sql, { language: "postgresql" })}`;
|
|
452
504
|
}
|
|
@@ -33,12 +33,12 @@ async function default_1(context, options) {
|
|
|
33
33
|
utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
|
|
34
34
|
}));
|
|
35
35
|
if (servicesToDelete.length) {
|
|
36
|
+
const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
|
|
36
37
|
if (await (0, prompt_1.confirm)({
|
|
37
|
-
force:
|
|
38
|
+
force: false,
|
|
38
39
|
nonInteractive: options.nonInteractive,
|
|
39
|
-
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${
|
|
40
|
-
|
|
41
|
-
.join("\n")}\nWould you like to delete these services?`,
|
|
40
|
+
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
|
|
41
|
+
default: false,
|
|
42
42
|
})) {
|
|
43
43
|
await Promise.all(servicesToDelete.map(async (s) => {
|
|
44
44
|
await client.deleteService(s.name);
|
|
@@ -52,22 +52,20 @@ async function default_1(context, options) {
|
|
|
52
52
|
return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
53
53
|
})
|
|
54
54
|
.map(async (s) => {
|
|
55
|
-
var _a, _b;
|
|
55
|
+
var _a, _b, _c;
|
|
56
56
|
const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
|
|
57
57
|
if (postgresDatasource) {
|
|
58
|
-
const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
|
|
59
|
-
const databaseId = (
|
|
58
|
+
const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
|
|
59
|
+
const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
|
|
60
60
|
if (!instanceId || !databaseId) {
|
|
61
61
|
return Promise.resolve();
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
63
|
+
return (0, provisionCloudSql_1.setupCloudSql)({
|
|
65
64
|
projectId,
|
|
66
65
|
location: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
67
66
|
instanceId,
|
|
68
67
|
databaseId,
|
|
69
|
-
|
|
70
|
-
waitForCreation: true,
|
|
68
|
+
requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
|
|
71
69
|
});
|
|
72
70
|
}
|
|
73
71
|
}));
|