firebase-tools 13.17.0 → 13.18.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/lib/commands/dataconnect-services-list.js +4 -3
- package/lib/commands/dataconnect-sql-grant.js +37 -0
- package/lib/commands/dataconnect-sql-migrate.js +2 -1
- package/lib/commands/deploy.js +2 -0
- package/lib/commands/index.js +1 -0
- package/lib/dataconnect/client.js +1 -1
- package/lib/dataconnect/dataplaneClient.js +1 -1
- package/lib/dataconnect/ensureApis.js +7 -1
- package/lib/dataconnect/load.js +3 -1
- package/lib/dataconnect/provisionCloudSql.js +34 -21
- package/lib/dataconnect/schemaMigration.js +126 -73
- package/lib/deploy/dataconnect/deploy.js +16 -13
- package/lib/deploy/dataconnect/prepare.js +36 -0
- package/lib/deploy/dataconnect/release.js +9 -2
- package/lib/deploy/extensions/prepare.js +22 -9
- package/lib/deploy/firestore/prepare.js +10 -0
- package/lib/deploy/firestore/release.js +3 -6
- package/lib/deploy/functions/checkIam.js +7 -2
- package/lib/deploy/functions/ensure.js +10 -2
- package/lib/deploy/functions/prepare.js +2 -2
- package/lib/deploy/index.js +9 -5
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/firestore/checkDatabaseType.js +10 -3
- package/lib/frameworks/angular/index.js +15 -3
- package/lib/frameworks/next/utils.js +1 -1
- package/lib/gcp/cloudsql/permissions.js +6 -1
- package/lib/gcp/secretManager.js +12 -5
- package/lib/init/features/dataconnect/index.js +24 -11
- package/lib/init/features/firestore/index.js +20 -1
- package/lib/init/features/firestore/indexes.js +4 -4
- package/package.json +1 -1
- package/schema/connector-yaml.json +43 -17
- package/templates/init/dataconnect/connector.yaml +0 -1
- package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +12 -0
- package/templates/init/dataconnect/dataconnect.yaml +1 -1
|
@@ -36,14 +36,15 @@ exports.command = new command_1.Command("dataconnect:services:list")
|
|
|
36
36
|
for (const service of services) {
|
|
37
37
|
const schema = (_a = (await client.getSchema(service.name))) !== null && _a !== void 0 ? _a : {
|
|
38
38
|
name: "",
|
|
39
|
-
|
|
39
|
+
datasources: [{}],
|
|
40
40
|
source: { files: [] },
|
|
41
41
|
};
|
|
42
42
|
const connectors = await client.listConnectors(service.name);
|
|
43
43
|
const serviceName = names.parseServiceName(service.name);
|
|
44
|
-
const
|
|
44
|
+
const postgresDatasource = schema === null || schema === void 0 ? void 0 : schema.datasources.find((d) => d.postgresql);
|
|
45
|
+
const instanceName = (_c = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance) !== null && _c !== void 0 ? _c : "";
|
|
45
46
|
const instanceId = instanceName.split("/").pop();
|
|
46
|
-
const dbId = (_e = (_d =
|
|
47
|
+
const dbId = (_e = (_d = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
|
|
47
48
|
const dbName = `CloudSQL Instance: ${instanceId}\nDatabase: ${dbId}`;
|
|
48
49
|
table.push([
|
|
49
50
|
serviceName.serviceId,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const projectUtils_1 = require("../projectUtils");
|
|
6
|
+
const ensureApis_1 = require("../dataconnect/ensureApis");
|
|
7
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
8
|
+
const fileUtils_1 = require("../dataconnect/fileUtils");
|
|
9
|
+
const schemaMigration_1 = require("../dataconnect/schemaMigration");
|
|
10
|
+
const requireAuth_1 = require("../requireAuth");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const permissions_1 = require("../gcp/cloudsql/permissions");
|
|
13
|
+
const allowedRoles = Object.keys(permissions_1.fdcSqlRoleMap);
|
|
14
|
+
exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
|
|
15
|
+
.description("Grants the SQL role <role> to the provided user or service account <email>.")
|
|
16
|
+
.option("-R, --role <role>", "The SQL role to grant. One of: owner, writer, or reader.")
|
|
17
|
+
.option("-E, --email <email>", "The email of the user or service account we would like to grant the role to.")
|
|
18
|
+
.before(requirePermissions_1.requirePermissions, ["firebasedataconnect.services.list"])
|
|
19
|
+
.before(requireAuth_1.requireAuth)
|
|
20
|
+
.action(async (serviceId, options) => {
|
|
21
|
+
const role = options.role;
|
|
22
|
+
const email = options.email;
|
|
23
|
+
if (!role) {
|
|
24
|
+
throw new error_1.FirebaseError("-R, --role <role> is required. Run the command with -h for more info.");
|
|
25
|
+
}
|
|
26
|
+
if (!email) {
|
|
27
|
+
throw new error_1.FirebaseError("-E, --email <email> is required. Run the command with -h for more info.");
|
|
28
|
+
}
|
|
29
|
+
if (!allowedRoles.includes(role.toLowerCase())) {
|
|
30
|
+
throw new error_1.FirebaseError(`Role should be one of ${allowedRoles.join(" | ")}.`);
|
|
31
|
+
}
|
|
32
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
33
|
+
await (0, ensureApis_1.ensureApis)(projectId);
|
|
34
|
+
const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
|
|
35
|
+
await (0, schemaMigration_1.grantRoleToUserInSchema)(options, serviceInfo.schema);
|
|
36
|
+
return { projectId, serviceId };
|
|
37
|
+
});
|
|
@@ -21,7 +21,7 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
|
21
21
|
.before(requireAuth_1.requireAuth)
|
|
22
22
|
.withForce("Execute any required database changes without prompting")
|
|
23
23
|
.action(async (serviceId, options) => {
|
|
24
|
-
var _a;
|
|
24
|
+
var _a, _b;
|
|
25
25
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
26
26
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
27
27
|
const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
|
|
@@ -33,6 +33,7 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
|
33
33
|
options,
|
|
34
34
|
schema: serviceInfo.schema,
|
|
35
35
|
validateOnly: true,
|
|
36
|
+
schemaValidation: (_b = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _b === void 0 ? void 0 : _b.schemaValidation,
|
|
36
37
|
});
|
|
37
38
|
if (diffs.length) {
|
|
38
39
|
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema sucessfully migrated! Run 'firebase deploy' to deploy your new schema to your Data Connect service.`);
|
package/lib/commands/deploy.js
CHANGED
|
@@ -86,6 +86,8 @@ exports.command = new command_1.Command("deploy")
|
|
|
86
86
|
"For data connect, can specify filters with colons to deploy only a service, connector, or schema" +
|
|
87
87
|
'(e.g. "--only dataconnect:serviceId,dataconnect:serviceId:connectorId,dataconnect:serviceId:schema"). ')
|
|
88
88
|
.option("--except <targets>", 'deploy to all targets except specified (e.g. "database")')
|
|
89
|
+
.option("--dry-run", "Perform a dry run of your deployment. Validates your changes and builds your code without deploying any changes to your project. " +
|
|
90
|
+
"In order to provide better validation, this may still enable APIs on the target project.")
|
|
89
91
|
.before(requireConfig_1.requireConfig)
|
|
90
92
|
.before((options) => {
|
|
91
93
|
options.filteredTargets = (0, filterTargets_1.filterTargets)(options, exports.VALID_DEPLOY_TARGETS);
|
package/lib/commands/index.js
CHANGED
|
@@ -207,6 +207,7 @@ function load(client) {
|
|
|
207
207
|
client.dataconnect.sql = {};
|
|
208
208
|
client.dataconnect.sql.diff = loadCommand("dataconnect-sql-diff");
|
|
209
209
|
client.dataconnect.sql.migrate = loadCommand("dataconnect-sql-migrate");
|
|
210
|
+
client.dataconnect.sql.grant = loadCommand("dataconnect-sql-grant");
|
|
210
211
|
client.dataconnect.sdk = {};
|
|
211
212
|
client.dataconnect.sdk.generate = loadCommand("dataconnect-sdk-generate");
|
|
212
213
|
client.target = loadCommand("target");
|
|
@@ -5,7 +5,7 @@ const api_1 = require("../api");
|
|
|
5
5
|
const apiv2_1 = require("../apiv2");
|
|
6
6
|
const operationPoller = require("../operation-poller");
|
|
7
7
|
const types = require("./types");
|
|
8
|
-
const DATACONNECT_API_VERSION = "
|
|
8
|
+
const DATACONNECT_API_VERSION = "v1beta";
|
|
9
9
|
const PAGE_SIZE_MAX = 100;
|
|
10
10
|
const dataconnectClient = () => new apiv2_1.Client({
|
|
11
11
|
urlPrefix: (0, api_1.dataconnectOrigin)(),
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.executeGraphQL = void 0;
|
|
4
4
|
const api_1 = require("../api");
|
|
5
5
|
const apiv2_1 = require("../apiv2");
|
|
6
|
-
const DATACONNECT_API_VERSION = "
|
|
6
|
+
const DATACONNECT_API_VERSION = "v1beta";
|
|
7
7
|
const dataconnectDataplaneClient = () => new apiv2_1.Client({
|
|
8
8
|
urlPrefix: (0, api_1.dataconnectOrigin)(),
|
|
9
9
|
apiVersion: DATACONNECT_API_VERSION,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureApis = void 0;
|
|
3
|
+
exports.ensureSparkApis = exports.ensureApis = void 0;
|
|
4
4
|
const api = require("../api");
|
|
5
5
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
6
6
|
async function ensureApis(projectId) {
|
|
@@ -10,3 +10,9 @@ async function ensureApis(projectId) {
|
|
|
10
10
|
await (0, ensureApiEnabled_1.ensure)(projectId, api.computeOrigin(), prefix);
|
|
11
11
|
}
|
|
12
12
|
exports.ensureApis = ensureApis;
|
|
13
|
+
async function ensureSparkApis(projectId) {
|
|
14
|
+
const prefix = "dataconnect";
|
|
15
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix);
|
|
16
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix);
|
|
17
|
+
}
|
|
18
|
+
exports.ensureSparkApis = ensureSparkApis;
|
package/lib/dataconnect/load.js
CHANGED
|
@@ -30,7 +30,9 @@ async function load(projectId, config, sourceDirectory) {
|
|
|
30
30
|
sourceDirectory: resolvedDir,
|
|
31
31
|
schema: {
|
|
32
32
|
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
33
|
-
|
|
33
|
+
datasources: [
|
|
34
|
+
(0, types_1.toDatasource)(projectId, dataConnectYaml.location, dataConnectYaml.schema.datasource),
|
|
35
|
+
],
|
|
34
36
|
source: {
|
|
35
37
|
files: schemaGQLs,
|
|
36
38
|
},
|
|
@@ -11,19 +11,22 @@ const freeTrial_1 = require("./freeTrial");
|
|
|
11
11
|
const error_1 = require("../error");
|
|
12
12
|
async function provisionCloudSql(args) {
|
|
13
13
|
let connectionName = "";
|
|
14
|
-
const { projectId, locationId, instanceId, databaseId, enableGoogleMlIntegration, waitForCreation, silent, } = args;
|
|
14
|
+
const { projectId, locationId, instanceId, databaseId, enableGoogleMlIntegration, waitForCreation, silent, dryRun, } = args;
|
|
15
15
|
try {
|
|
16
16
|
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
17
17
|
silent || utils.logLabeledBullet("dataconnect", `Found existing instance ${instanceId}.`);
|
|
18
18
|
connectionName = (existingInstance === null || existingInstance === void 0 ? void 0 : existingInstance.connectionName) || "";
|
|
19
19
|
const why = getUpdateReason(existingInstance, enableGoogleMlIntegration);
|
|
20
20
|
if (why) {
|
|
21
|
+
const cta = dryRun
|
|
22
|
+
? `It will be updated on your next deploy.`
|
|
23
|
+
: `Updating instance. This may take a few minutes...`;
|
|
21
24
|
silent ||
|
|
22
|
-
utils.logLabeledBullet("dataconnect", `Instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
utils.logLabeledBullet("dataconnect", `Instance ${instanceId} settings not compatible with Firebase Data Connect. ` + cta + why);
|
|
26
|
+
if (!dryRun) {
|
|
27
|
+
await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, enableGoogleMlIntegration), "Updating your instance...");
|
|
28
|
+
silent || utils.logLabeledBullet("dataconnect", "Instance updated");
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
catch (err) {
|
|
@@ -35,19 +38,23 @@ async function provisionCloudSql(args) {
|
|
|
35
38
|
(0, freeTrial_1.printFreeTrialUnavailable)(projectId, freeTrialInstanceId);
|
|
36
39
|
throw new error_1.FirebaseError("Free trial unavailable.");
|
|
37
40
|
}
|
|
41
|
+
const cta = dryRun ? "It will be created on your next deploy" : "Creating it now.";
|
|
38
42
|
silent ||
|
|
39
|
-
utils.logLabeledBullet("dataconnect", `CloudSQL instance '${instanceId}' not found
|
|
43
|
+
utils.logLabeledBullet("dataconnect", `CloudSQL instance '${instanceId}' not found.` +
|
|
44
|
+
cta +
|
|
40
45
|
`\nThis instance is provided under the terms of the Data Connect free trial ${(0, freeTrial_1.freeTrialTermsLink)()}` +
|
|
41
46
|
`\nMonitor the progress at ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}`);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if (!dryRun) {
|
|
48
|
+
const newInstance = await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.createInstance(projectId, locationId, instanceId, enableGoogleMlIntegration, waitForCreation), "Creating your instance...");
|
|
49
|
+
if (newInstance) {
|
|
50
|
+
silent || utils.logLabeledBullet("dataconnect", "Instance created");
|
|
51
|
+
connectionName = (newInstance === null || newInstance === void 0 ? void 0 : newInstance.connectionName) || "";
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
silent ||
|
|
55
|
+
utils.logLabeledBullet("dataconnect", "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.");
|
|
56
|
+
return connectionName;
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
try {
|
|
@@ -56,17 +63,23 @@ async function provisionCloudSql(args) {
|
|
|
56
63
|
}
|
|
57
64
|
catch (err) {
|
|
58
65
|
if (err.status === 404) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
if (dryRun) {
|
|
67
|
+
silent ||
|
|
68
|
+
utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found. It will be created on your next deploy.`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
silent ||
|
|
72
|
+
utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
|
|
73
|
+
await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
|
|
74
|
+
silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
|
|
75
|
+
}
|
|
63
76
|
}
|
|
64
77
|
else {
|
|
65
78
|
logger_1.logger.debug(`Unexpected error from CloudSQL: ${err}`);
|
|
66
79
|
silent || utils.logLabeledWarning("dataconnect", `Database ${databaseId} is not accessible.`);
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
|
-
if (enableGoogleMlIntegration) {
|
|
82
|
+
if (enableGoogleMlIntegration && !dryRun) {
|
|
70
83
|
await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
|
|
71
84
|
}
|
|
72
85
|
return connectionName;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.migrateSchema = exports.diffSchema = void 0;
|
|
3
|
+
exports.grantRoleToUserInSchema = exports.migrateSchema = exports.diffSchema = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const sql_formatter_1 = require("sql-formatter");
|
|
6
6
|
const types_1 = require("./types");
|
|
7
7
|
const client_1 = require("./client");
|
|
8
8
|
const connect_1 = require("../gcp/cloudsql/connect");
|
|
9
9
|
const permissions_1 = require("../gcp/cloudsql/permissions");
|
|
10
|
+
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
11
|
+
const projectUtils_1 = require("../projectUtils");
|
|
10
12
|
const prompt_1 = require("../prompt");
|
|
11
13
|
const logger_1 = require("../logger");
|
|
12
14
|
const error_1 = require("../error");
|
|
@@ -17,15 +19,9 @@ async function diffSchema(schema, schemaValidation) {
|
|
|
17
19
|
const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
|
|
18
20
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
19
21
|
let diffs = [];
|
|
20
|
-
let validationMode = "
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
validationMode = "COMPATIBLE";
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
validationMode = schemaValidation;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
22
|
+
let validationMode = experiments.isEnabled("fdccompatiblemode")
|
|
23
|
+
? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
|
|
24
|
+
: "STRICT";
|
|
29
25
|
setSchemaValidationMode(schema, validationMode);
|
|
30
26
|
try {
|
|
31
27
|
if (!schemaValidation && experiments.isEnabled("fdccompatiblemode")) {
|
|
@@ -56,33 +52,31 @@ async function diffSchema(schema, schemaValidation) {
|
|
|
56
52
|
diffs = incompatible.diffs;
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
|
-
if (experiments.isEnabled("fdccompatiblemode")) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
if (experiments.isEnabled("fdccompatiblemode") && !schemaValidation) {
|
|
56
|
+
validationMode = "STRICT";
|
|
57
|
+
setSchemaValidationMode(schema, validationMode);
|
|
58
|
+
try {
|
|
59
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `generating schema changes, including optional changes...`);
|
|
60
|
+
await (0, client_1.upsertSchema)(schema, true);
|
|
61
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
65
|
+
throw err;
|
|
67
66
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (incompatible) {
|
|
74
|
-
if (!diffsEqual(diffs, incompatible.diffs)) {
|
|
75
|
-
if (diffs.length === 0) {
|
|
76
|
-
displaySchemaChanges(incompatible, "STRICT_AFTER_COMPATIBLE", instanceName, databaseId);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
80
|
-
}
|
|
81
|
-
diffs = incompatible.diffs;
|
|
67
|
+
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
68
|
+
if (incompatible) {
|
|
69
|
+
if (!diffsEqual(diffs, incompatible.diffs)) {
|
|
70
|
+
if (diffs.length === 0) {
|
|
71
|
+
displaySchemaChanges(incompatible, "STRICT_AFTER_COMPATIBLE", instanceName, databaseId);
|
|
82
72
|
}
|
|
83
73
|
else {
|
|
84
|
-
(
|
|
74
|
+
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
85
75
|
}
|
|
76
|
+
diffs = incompatible.diffs;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
86
80
|
}
|
|
87
81
|
}
|
|
88
82
|
}
|
|
@@ -91,10 +85,13 @@ async function diffSchema(schema, schemaValidation) {
|
|
|
91
85
|
}
|
|
92
86
|
exports.diffSchema = diffSchema;
|
|
93
87
|
async function migrateSchema(args) {
|
|
94
|
-
const { options, schema, validateOnly } = args;
|
|
88
|
+
const { options, schema, validateOnly, schemaValidation } = args;
|
|
95
89
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
96
90
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
97
|
-
|
|
91
|
+
let diffs = [];
|
|
92
|
+
let validationMode = experiments.isEnabled("fdccompatiblemode")
|
|
93
|
+
? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
|
|
94
|
+
: "STRICT";
|
|
98
95
|
setSchemaValidationMode(schema, validationMode);
|
|
99
96
|
try {
|
|
100
97
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
@@ -111,7 +108,6 @@ async function migrateSchema(args) {
|
|
|
111
108
|
}
|
|
112
109
|
const migrationMode = await promptForSchemaMigration(options, instanceName, databaseId, incompatible, validateOnly, validationMode);
|
|
113
110
|
const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
|
|
114
|
-
let diffs = [];
|
|
115
111
|
if (incompatible) {
|
|
116
112
|
diffs = await handleIncompatibleSchemaError({
|
|
117
113
|
options,
|
|
@@ -127,11 +123,54 @@ async function migrateSchema(args) {
|
|
|
127
123
|
if (!validateOnly) {
|
|
128
124
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
129
125
|
}
|
|
130
|
-
return diffs;
|
|
131
126
|
}
|
|
132
|
-
|
|
127
|
+
if (experiments.isEnabled("fdccompatiblemode") && !schemaValidation) {
|
|
128
|
+
validationMode = "STRICT";
|
|
129
|
+
setSchemaValidationMode(schema, validationMode);
|
|
130
|
+
try {
|
|
131
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err.status !== 400) {
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
138
|
+
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
139
|
+
if (!incompatible && !invalidConnectors.length) {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
const migrationMode = await promptForSchemaMigration(options, instanceName, databaseId, incompatible, validateOnly, "STRICT_AFTER_COMPATIBLE");
|
|
143
|
+
if (incompatible) {
|
|
144
|
+
const maybeDiffs = await handleIncompatibleSchemaError({
|
|
145
|
+
options,
|
|
146
|
+
databaseId,
|
|
147
|
+
instanceId,
|
|
148
|
+
incompatibleSchemaError: incompatible,
|
|
149
|
+
choice: migrationMode,
|
|
150
|
+
});
|
|
151
|
+
diffs = diffs.concat(maybeDiffs);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return diffs;
|
|
133
156
|
}
|
|
134
157
|
exports.migrateSchema = migrateSchema;
|
|
158
|
+
async function grantRoleToUserInSchema(options, schema) {
|
|
159
|
+
const role = options.role;
|
|
160
|
+
const email = options.email;
|
|
161
|
+
const { instanceId, databaseId } = getIdentifiers(schema);
|
|
162
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
163
|
+
const { user, mode } = (0, connect_1.toDatabaseUser)(email);
|
|
164
|
+
const fdcSqlRole = permissions_1.fdcSqlRoleMap[role](databaseId);
|
|
165
|
+
const userIsCSQLAdmin = await (0, permissions_1.iamUserIsCSQLAdmin)(options);
|
|
166
|
+
if (!userIsCSQLAdmin) {
|
|
167
|
+
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}`);
|
|
168
|
+
}
|
|
169
|
+
await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
|
|
170
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
171
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
|
|
172
|
+
}
|
|
173
|
+
exports.grantRoleToUserInSchema = grantRoleToUserInSchema;
|
|
135
174
|
function diffsEqual(x, y) {
|
|
136
175
|
if (x.length !== y.length) {
|
|
137
176
|
return false;
|
|
@@ -146,17 +185,21 @@ function diffsEqual(x, y) {
|
|
|
146
185
|
return true;
|
|
147
186
|
}
|
|
148
187
|
function setSchemaValidationMode(schema, schemaValidation) {
|
|
149
|
-
if (experiments.isEnabled("fdccompatiblemode")
|
|
150
|
-
schema.
|
|
188
|
+
if (experiments.isEnabled("fdccompatiblemode")) {
|
|
189
|
+
const postgresDatasource = schema.datasources.find((d) => d.postgresql);
|
|
190
|
+
if (postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) {
|
|
191
|
+
postgresDatasource.postgresql.schemaValidation = schemaValidation;
|
|
192
|
+
}
|
|
151
193
|
}
|
|
152
194
|
}
|
|
153
195
|
function getIdentifiers(schema) {
|
|
154
196
|
var _a, _b;
|
|
155
|
-
const
|
|
197
|
+
const postgresDatasource = schema.datasources.find((d) => d.postgresql);
|
|
198
|
+
const databaseId = (_a = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
|
|
156
199
|
if (!databaseId) {
|
|
157
|
-
throw new error_1.FirebaseError("
|
|
200
|
+
throw new error_1.FirebaseError("Service does not have a postgres datasource, cannot migrate");
|
|
158
201
|
}
|
|
159
|
-
const instanceName = (_b =
|
|
202
|
+
const instanceName = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance;
|
|
160
203
|
if (!instanceName) {
|
|
161
204
|
throw new error_1.FirebaseError("tried to migrate schema but instance name was not provided in dataconnect.yaml");
|
|
162
205
|
}
|
|
@@ -218,28 +261,38 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
218
261
|
}
|
|
219
262
|
return [];
|
|
220
263
|
}
|
|
221
|
-
async function promptForSchemaMigration(options, instanceName, databaseId, err, validateOnly,
|
|
264
|
+
async function promptForSchemaMigration(options, instanceName, databaseId, err, validateOnly, validationMode) {
|
|
222
265
|
if (!err) {
|
|
223
266
|
return "none";
|
|
224
267
|
}
|
|
225
|
-
|
|
268
|
+
if (validationMode === "STRICT_AFTER_COMPATIBLE" && (options.nonInteractive || options.force)) {
|
|
269
|
+
return "none";
|
|
270
|
+
}
|
|
271
|
+
displaySchemaChanges(err, validationMode, instanceName, databaseId);
|
|
226
272
|
if (!options.nonInteractive) {
|
|
227
273
|
if (validateOnly && options.force) {
|
|
228
274
|
return "all";
|
|
229
275
|
}
|
|
230
|
-
const
|
|
231
|
-
?
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
276
|
+
const message = validationMode === "STRICT_AFTER_COMPATIBLE"
|
|
277
|
+
? `Would you like to execute these optional changes against ${databaseId} in your CloudSQL instance ${instanceName}?`
|
|
278
|
+
: `Would you like to execute these changes against ${databaseId} in your CloudSQL instance ${instanceName}?`;
|
|
279
|
+
let executeChangePrompt = "Execute changes";
|
|
280
|
+
if (validationMode === "STRICT_AFTER_COMPATIBLE") {
|
|
281
|
+
executeChangePrompt = "Execute optional changes";
|
|
282
|
+
}
|
|
283
|
+
if (err.destructive) {
|
|
284
|
+
executeChangePrompt = executeChangePrompt + " (including destructive changes)";
|
|
285
|
+
}
|
|
286
|
+
const choices = [
|
|
287
|
+
{ name: executeChangePrompt, value: "all" },
|
|
288
|
+
{ name: "Abort changes", value: "none" },
|
|
289
|
+
];
|
|
290
|
+
const defaultValue = validationMode === "STRICT_AFTER_COMPATIBLE" ? "none" : "all";
|
|
239
291
|
return await (0, prompt_1.promptOnce)({
|
|
240
|
-
message:
|
|
292
|
+
message: message,
|
|
241
293
|
type: "list",
|
|
242
294
|
choices,
|
|
295
|
+
default: defaultValue,
|
|
243
296
|
});
|
|
244
297
|
}
|
|
245
298
|
if (!validateOnly) {
|
|
@@ -261,10 +314,7 @@ async function promptForInvalidConnectorError(options, serviceName, invalidConne
|
|
|
261
314
|
}
|
|
262
315
|
displayInvalidConnectors(invalidConnectors);
|
|
263
316
|
if (validateOnly) {
|
|
264
|
-
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
throw new error_1.FirebaseError(`Command aborted. If you'd like to migrate it anyway, you may override with --force.`);
|
|
317
|
+
return false;
|
|
268
318
|
}
|
|
269
319
|
if (options.force) {
|
|
270
320
|
return true;
|
|
@@ -297,17 +347,20 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
297
347
|
source: {
|
|
298
348
|
files: [],
|
|
299
349
|
},
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
350
|
+
datasources: [
|
|
351
|
+
{
|
|
352
|
+
postgresql: {
|
|
353
|
+
database: databaseId,
|
|
354
|
+
cloudSql: {
|
|
355
|
+
instance: instanceId,
|
|
356
|
+
},
|
|
305
357
|
},
|
|
306
358
|
},
|
|
307
|
-
|
|
359
|
+
],
|
|
308
360
|
};
|
|
309
361
|
}
|
|
310
|
-
const
|
|
362
|
+
const postgresDatasource = currentSchema.datasources.find((d) => d.postgresql);
|
|
363
|
+
const postgresql = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql;
|
|
311
364
|
if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance) !== instanceId) {
|
|
312
365
|
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\nFrom ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance}\nTo ${instanceId}`);
|
|
313
366
|
}
|
|
@@ -328,22 +381,22 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
328
381
|
logger_1.logger.debug(err);
|
|
329
382
|
}
|
|
330
383
|
}
|
|
331
|
-
function displaySchemaChanges(error,
|
|
384
|
+
function displaySchemaChanges(error, validationMode, instanceName, databaseId) {
|
|
332
385
|
switch (error.violationType) {
|
|
333
386
|
case "INCOMPATIBLE_SCHEMA":
|
|
334
387
|
{
|
|
335
388
|
let message;
|
|
336
|
-
if (
|
|
389
|
+
if (validationMode === "COMPATIBLE") {
|
|
337
390
|
message =
|
|
338
|
-
"Your
|
|
391
|
+
"Your PostgreSQL database " +
|
|
339
392
|
databaseId +
|
|
340
393
|
" in your CloudSQL instance " +
|
|
341
394
|
instanceName +
|
|
342
|
-
". " +
|
|
395
|
+
" must be migrated in order to be compatible with your application schema. " +
|
|
343
396
|
"The following SQL statements will migrate your database schema to be compatible with your new Data Connect schema.\n" +
|
|
344
397
|
error.diffs.map(toString).join("\n");
|
|
345
398
|
}
|
|
346
|
-
else if (
|
|
399
|
+
else if (validationMode === "STRICT_AFTER_COMPATIBLE") {
|
|
347
400
|
message =
|
|
348
401
|
"Your new application schema is compatible with the schema of your PostgreSQL database " +
|
|
349
402
|
databaseId +
|
|
@@ -355,11 +408,11 @@ function displaySchemaChanges(error, schemaValidation, instanceName, databaseId)
|
|
|
355
408
|
}
|
|
356
409
|
else {
|
|
357
410
|
message =
|
|
358
|
-
"Your
|
|
411
|
+
"Your PostgreSQL database " +
|
|
359
412
|
databaseId +
|
|
360
413
|
" in your CloudSQL instance " +
|
|
361
414
|
instanceName +
|
|
362
|
-
". " +
|
|
415
|
+
" must be migrated in order to match your application schema. " +
|
|
363
416
|
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
364
417
|
error.diffs.map(toString).join("\n");
|
|
365
418
|
}
|
|
@@ -44,20 +44,23 @@ async function default_1(context, options) {
|
|
|
44
44
|
})
|
|
45
45
|
.map(async (s) => {
|
|
46
46
|
var _a, _b;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
|
|
48
|
+
if (postgresDatasource) {
|
|
49
|
+
const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
|
|
50
|
+
const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
|
|
51
|
+
if (!instanceId || !databaseId) {
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
|
|
55
|
+
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
56
|
+
projectId,
|
|
57
|
+
locationId: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
58
|
+
instanceId,
|
|
59
|
+
databaseId,
|
|
60
|
+
enableGoogleMlIntegration,
|
|
61
|
+
waitForCreation: true,
|
|
62
|
+
});
|
|
51
63
|
}
|
|
52
|
-
const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
|
|
53
|
-
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
54
|
-
projectId,
|
|
55
|
-
locationId: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
56
|
-
instanceId,
|
|
57
|
-
databaseId,
|
|
58
|
-
enableGoogleMlIntegration,
|
|
59
|
-
waitForCreation: true,
|
|
60
|
-
});
|
|
61
64
|
}));
|
|
62
65
|
return;
|
|
63
66
|
}
|