firebase-tools 14.12.1 → 14.13.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/fileUtils.js +5 -6
- package/lib/dataconnect/freeTrial.js +16 -39
- package/lib/dataconnect/provisionCloudSql.js +67 -70
- package/lib/dataconnect/schemaMigration.js +75 -47
- package/lib/deploy/dataconnect/deploy.js +9 -11
- package/lib/deploy/dataconnect/prepare.js +7 -10
- package/lib/deploy/dataconnect/release.js +11 -5
- 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 +6 -4
- package/lib/deploy/functions/pricing.js +12 -5
- package/lib/deploy/functions/release/fabricator.js +25 -3
- package/lib/emulator/controller.js +2 -1
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/functionsEmulator.js +9 -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/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 +1 -1
- package/lib/gcp/cloudsql/permissionsSetup.js +13 -15
- 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 +32 -16
- 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 +3 -0
- package/lib/extensions/resolveSource.js +0 -24
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm]
|
|
1
|
+
# Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm] [](https://cursor.com/en/install-mcp?name=firebase&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImZpcmViYXNlLXRvb2xzIiwiZXhwZXJpbWVudGFsOm1jcCIsIi0tZGlyIiwiLiJdfQ==)
|
|
2
2
|
|
|
3
3
|
The Firebase Command Line Interface (CLI) Tools can be used to test, manage, and deploy your Firebase project from the command line.
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ exports.command = new command_1.Command("dataconnect:services:list")
|
|
|
17
17
|
"dataconnect.connectors.list",
|
|
18
18
|
])
|
|
19
19
|
.action(async (options) => {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
21
21
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
22
22
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
23
23
|
const services = await client.listAllServices(projectId);
|
|
@@ -42,15 +42,15 @@ exports.command = new command_1.Command("dataconnect:services:list")
|
|
|
42
42
|
const connectors = await client.listConnectors(service.name);
|
|
43
43
|
const serviceName = names.parseServiceName(service.name);
|
|
44
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 &&
|
|
45
|
+
const instanceName = (_d = (_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) !== null && _d !== void 0 ? _d : "";
|
|
46
46
|
const instanceId = instanceName.split("/").pop();
|
|
47
|
-
const dbId = (
|
|
47
|
+
const dbId = (_f = (_e = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _e === void 0 ? void 0 : _e.database) !== null && _f !== void 0 ? _f : "";
|
|
48
48
|
const dbName = `CloudSQL Instance: ${instanceId}\nDatabase: ${dbId}`;
|
|
49
49
|
table.push([
|
|
50
50
|
serviceName.serviceId,
|
|
51
51
|
serviceName.location,
|
|
52
52
|
dbName,
|
|
53
|
-
(
|
|
53
|
+
(_g = schema === null || schema === void 0 ? void 0 : schema.updateTime) !== null && _g !== void 0 ? _g : "",
|
|
54
54
|
"",
|
|
55
55
|
"",
|
|
56
56
|
]);
|
|
@@ -66,7 +66,7 @@ exports.command = new command_1.Command("dataconnect:services:list")
|
|
|
66
66
|
table.push(["", "", "", "", connectorName.connectorId, conn.updateTime]);
|
|
67
67
|
serviceJson.connectors.push({
|
|
68
68
|
connectorId: connectorName.connectorId,
|
|
69
|
-
connectorLastUpdated: (
|
|
69
|
+
connectorLastUpdated: (_h = conn.updateTime) !== null && _h !== void 0 ? _h : "",
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
jsonOutput.services.push(serviceJson);
|
|
@@ -10,6 +10,7 @@ const schemaMigration_1 = require("../dataconnect/schemaMigration");
|
|
|
10
10
|
const requireAuth_1 = require("../requireAuth");
|
|
11
11
|
const error_1 = require("../error");
|
|
12
12
|
const permissionsSetup_1 = require("../gcp/cloudsql/permissionsSetup");
|
|
13
|
+
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
13
14
|
const allowedRoles = Object.keys(permissionsSetup_1.fdcSqlRoleMap);
|
|
14
15
|
exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
|
|
15
16
|
.description("grants the SQL role <role> to the provided user or service account <email>")
|
|
@@ -29,6 +30,10 @@ exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
|
|
|
29
30
|
if (!allowedRoles.includes(role.toLowerCase())) {
|
|
30
31
|
throw new error_1.FirebaseError(`Role should be one of ${allowedRoles.join(" | ")}.`);
|
|
31
32
|
}
|
|
33
|
+
const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
|
|
34
|
+
if (!userIsCSQLAdmin) {
|
|
35
|
+
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 ${role} to ${email}`);
|
|
36
|
+
}
|
|
32
37
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
33
38
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
34
39
|
const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
|
|
@@ -12,7 +12,6 @@ const permissionsSetup_1 = require("../gcp/cloudsql/permissionsSetup");
|
|
|
12
12
|
const permissions_1 = require("../gcp/cloudsql/permissions");
|
|
13
13
|
const schemaMigration_1 = require("../dataconnect/schemaMigration");
|
|
14
14
|
const connect_1 = require("../gcp/cloudsql/connect");
|
|
15
|
-
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
16
15
|
exports.command = new command_1.Command("dataconnect:sql:setup [serviceId]")
|
|
17
16
|
.description("set up your CloudSQL database")
|
|
18
17
|
.before(requirePermissions_1.requirePermissions, [
|
|
@@ -33,8 +32,7 @@ exports.command = new command_1.Command("dataconnect:sql:setup [serviceId]")
|
|
|
33
32
|
}
|
|
34
33
|
const { serviceName, instanceName, databaseId } = (0, schemaMigration_1.getIdentifiers)(serviceInfo.schema);
|
|
35
34
|
await (0, schemaMigration_1.ensureServiceIsConnectedToCloudSql)(serviceName, instanceName, databaseId, true);
|
|
36
|
-
|
|
37
|
-
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
35
|
+
await (0, connect_1.setupIAMUsers)(instanceId, options);
|
|
38
36
|
const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
|
|
39
37
|
await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options);
|
|
40
38
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getIssueDetails = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const api_1 = require("../api");
|
|
8
|
+
const TIMEOUT = 10000;
|
|
9
|
+
const apiClient = new apiv2_1.Client({
|
|
10
|
+
urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
|
|
11
|
+
apiVersion: "v1alpha",
|
|
12
|
+
});
|
|
13
|
+
async function getIssueDetails(appId, issueId) {
|
|
14
|
+
try {
|
|
15
|
+
const requestProjectNumber = parseProjectNumber(appId);
|
|
16
|
+
if (requestProjectNumber === undefined) {
|
|
17
|
+
throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
|
|
18
|
+
}
|
|
19
|
+
const response = await apiClient.request({
|
|
20
|
+
method: "GET",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
},
|
|
24
|
+
path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}`,
|
|
25
|
+
timeout: TIMEOUT,
|
|
26
|
+
});
|
|
27
|
+
return response.body;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
logger_1.logger.debug(err.message);
|
|
31
|
+
throw new error_1.FirebaseError(`Failed to fetch the issue details for the Firebase AppId ${appId}, IssueId ${issueId}. Error: ${err}.`, { original: err });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.getIssueDetails = getIssueDetails;
|
|
35
|
+
function parseProjectNumber(appId) {
|
|
36
|
+
const appIdParts = appId.split(":");
|
|
37
|
+
if (appIdParts.length > 1) {
|
|
38
|
+
return appIdParts[1];
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSampleCrash = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const api_1 = require("../api");
|
|
8
|
+
const TIMEOUT = 10000;
|
|
9
|
+
const apiClient = new apiv2_1.Client({
|
|
10
|
+
urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
|
|
11
|
+
apiVersion: "v1alpha",
|
|
12
|
+
});
|
|
13
|
+
async function getSampleCrash(appId, issueId, sampleCount, variantId) {
|
|
14
|
+
try {
|
|
15
|
+
const queryParams = new URLSearchParams();
|
|
16
|
+
queryParams.set("filter.issue.id", issueId);
|
|
17
|
+
queryParams.set("page_size", String(sampleCount));
|
|
18
|
+
if (variantId) {
|
|
19
|
+
queryParams.set("filter.issue.variant_id", variantId);
|
|
20
|
+
}
|
|
21
|
+
const requestProjectNumber = parseProjectNumber(appId);
|
|
22
|
+
if (requestProjectNumber === undefined) {
|
|
23
|
+
throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
|
|
24
|
+
}
|
|
25
|
+
const response = await apiClient.request({
|
|
26
|
+
method: "GET",
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
path: `/projects/${requestProjectNumber}/apps/${appId}/events`,
|
|
31
|
+
queryParams: queryParams,
|
|
32
|
+
timeout: TIMEOUT,
|
|
33
|
+
});
|
|
34
|
+
return response.body;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logger_1.logger.debug(err.message);
|
|
38
|
+
throw new error_1.FirebaseError(`Failed to fetch the same crash for the Firebase AppId ${appId}, IssueId ${issueId}. Error: ${err}.`, { original: err });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.getSampleCrash = getSampleCrash;
|
|
42
|
+
function parseProjectNumber(appId) {
|
|
43
|
+
const appIdParts = appId.split(":");
|
|
44
|
+
if (appIdParts.length > 1) {
|
|
45
|
+
return appIdParts[1];
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
@@ -30,19 +30,27 @@ async function listAllServices(projectId) {
|
|
|
30
30
|
}
|
|
31
31
|
exports.listAllServices = listAllServices;
|
|
32
32
|
async function createService(projectId, locationId, serviceId) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
try {
|
|
34
|
+
const op = await dataconnectClient().post(`/projects/${projectId}/locations/${locationId}/services`, {
|
|
35
|
+
name: `projects/${projectId}/locations/${locationId}/services/${serviceId}`,
|
|
36
|
+
}, {
|
|
37
|
+
queryParams: {
|
|
38
|
+
service_id: serviceId,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
const pollRes = await operationPoller.pollOperation({
|
|
42
|
+
apiOrigin: (0, api_1.dataconnectOrigin)(),
|
|
43
|
+
apiVersion: DATACONNECT_API_VERSION,
|
|
44
|
+
operationResourceName: op.body.name,
|
|
45
|
+
});
|
|
46
|
+
return pollRes;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err.status !== 409) {
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
exports.createService = createService;
|
|
48
56
|
async function deleteService(serviceName) {
|
|
@@ -89,14 +97,14 @@ async function listSchemas(serviceName, fields = []) {
|
|
|
89
97
|
return schemas;
|
|
90
98
|
}
|
|
91
99
|
exports.listSchemas = listSchemas;
|
|
92
|
-
async function upsertSchema(schema, validateOnly = false) {
|
|
100
|
+
async function upsertSchema(schema, validateOnly = false, async = false) {
|
|
93
101
|
const op = await dataconnectClient().patch(`${schema.name}`, schema, {
|
|
94
102
|
queryParams: {
|
|
95
103
|
allowMissing: "true",
|
|
96
104
|
validateOnly: validateOnly ? "true" : "false",
|
|
97
105
|
},
|
|
98
106
|
});
|
|
99
|
-
if (validateOnly) {
|
|
107
|
+
if (validateOnly || async) {
|
|
100
108
|
return;
|
|
101
109
|
}
|
|
102
110
|
return operationPoller.pollOperation({
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureGIFApis = exports.
|
|
3
|
+
exports.ensureGIFApis = exports.ensureApis = void 0;
|
|
4
4
|
const api = require("../api");
|
|
5
5
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
6
6
|
const prefix = "dataconnect";
|
|
7
7
|
async function ensureApis(projectId) {
|
|
8
|
-
await
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
await Promise.all([
|
|
9
|
+
(0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix),
|
|
10
|
+
(0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix),
|
|
11
|
+
]);
|
|
11
12
|
}
|
|
12
13
|
exports.ensureApis = ensureApis;
|
|
13
|
-
async function ensureSparkApis(projectId) {
|
|
14
|
-
await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix);
|
|
15
|
-
await (0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix);
|
|
16
|
-
}
|
|
17
|
-
exports.ensureSparkApis = ensureSparkApis;
|
|
18
14
|
async function ensureGIFApis(projectId) {
|
|
19
15
|
await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudAiCompanionOrigin(), prefix);
|
|
20
16
|
}
|
|
@@ -4,6 +4,7 @@ exports.getFrameworksFromPackageJson = exports.frameworksMap = exports.SUPPORTED
|
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const clc = require("colorette");
|
|
7
|
+
const glob_1 = require("glob");
|
|
7
8
|
const error_1 = require("../error");
|
|
8
9
|
const types_1 = require("./types");
|
|
9
10
|
const utils_1 = require("../utils");
|
|
@@ -57,14 +58,12 @@ async function readGQLFiles(sourceDir) {
|
|
|
57
58
|
if (!fs.existsSync(sourceDir)) {
|
|
58
59
|
return [];
|
|
59
60
|
}
|
|
60
|
-
const files = await
|
|
61
|
-
return files
|
|
62
|
-
.filter((f) => f.endsWith(".gql") || f.endsWith(".graphql"))
|
|
63
|
-
.map((f) => toFile(sourceDir, f));
|
|
61
|
+
const files = await (0, glob_1.glob)("**/*.{gql,graphql}", { cwd: sourceDir, absolute: true, nodir: true });
|
|
62
|
+
return files.map((f) => toFile(sourceDir, f));
|
|
64
63
|
}
|
|
65
64
|
exports.readGQLFiles = readGQLFiles;
|
|
66
|
-
function toFile(sourceDir,
|
|
67
|
-
const
|
|
65
|
+
function toFile(sourceDir, fullPath) {
|
|
66
|
+
const relPath = path.relative(sourceDir, fullPath);
|
|
68
67
|
if (!fs.existsSync(fullPath)) {
|
|
69
68
|
throw new error_1.FirebaseError(`file ${fullPath} not found`);
|
|
70
69
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.upgradeInstructions = exports.
|
|
3
|
+
exports.upgradeInstructions = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
|
|
6
|
-
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
7
6
|
const utils = require("../utils");
|
|
8
7
|
function freeTrialTermsLink() {
|
|
9
8
|
return "https://firebase.google.com/pricing";
|
|
@@ -11,7 +10,6 @@ function freeTrialTermsLink() {
|
|
|
11
10
|
exports.freeTrialTermsLink = freeTrialTermsLink;
|
|
12
11
|
const FREE_TRIAL_METRIC = "sqladmin.googleapis.com/fdc_lifetime_free_trial_per_project";
|
|
13
12
|
async function checkFreeTrialInstanceUsed(projectId) {
|
|
14
|
-
utils.logLabeledBullet("dataconnect", "Checking Cloud SQL no cost trial eligibility...");
|
|
15
13
|
const past7d = new Date();
|
|
16
14
|
past7d.setDate(past7d.getDate() - 7);
|
|
17
15
|
const query = {
|
|
@@ -19,53 +17,32 @@ async function checkFreeTrialInstanceUsed(projectId) {
|
|
|
19
17
|
"interval.endTime": new Date().toJSON(),
|
|
20
18
|
"interval.startTime": past7d.toJSON(),
|
|
21
19
|
};
|
|
20
|
+
let used = true;
|
|
22
21
|
try {
|
|
23
22
|
const ts = await (0, cloudmonitoring_1.queryTimeSeries)(query, projectId);
|
|
24
|
-
let used = true;
|
|
25
23
|
if (ts.length) {
|
|
26
24
|
used = ts[0].points.some((p) => p.value.int64Value);
|
|
27
25
|
}
|
|
28
|
-
if (used) {
|
|
29
|
-
utils.logLabeledWarning("dataconnect", "CloudSQL no cost trial has already been used on this project.");
|
|
30
|
-
}
|
|
31
|
-
return used;
|
|
32
26
|
}
|
|
33
27
|
catch (err) {
|
|
28
|
+
used = false;
|
|
29
|
+
}
|
|
30
|
+
if (used) {
|
|
31
|
+
utils.logLabeledWarning("dataconnect", "CloudSQL no cost trial has already been used on this project.");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
34
|
utils.logLabeledSuccess("dataconnect", "CloudSQL no cost trial available!");
|
|
35
|
-
return false;
|
|
36
35
|
}
|
|
36
|
+
return used;
|
|
37
37
|
}
|
|
38
38
|
exports.checkFreeTrialInstanceUsed = checkFreeTrialInstanceUsed;
|
|
39
|
-
async function getFreeTrialInstanceId(projectId) {
|
|
40
|
-
var _a;
|
|
41
|
-
const instances = await (0, cloudsqladmin_1.listInstances)(projectId);
|
|
42
|
-
return (_a = instances.find((i) => { var _a; return ((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft"; })) === null || _a === void 0 ? void 0 : _a.name;
|
|
43
|
-
}
|
|
44
|
-
exports.getFreeTrialInstanceId = getFreeTrialInstanceId;
|
|
45
|
-
async function isFreeTrialError(err, projectId) {
|
|
46
|
-
return err.message.includes("Quota Exhausted") && (await checkFreeTrialInstanceUsed(projectId))
|
|
47
|
-
? true
|
|
48
|
-
: false;
|
|
49
|
-
}
|
|
50
|
-
exports.isFreeTrialError = isFreeTrialError;
|
|
51
|
-
function printFreeTrialUnavailable(projectId, configYamlPath, instanceId) {
|
|
52
|
-
if (!instanceId) {
|
|
53
|
-
utils.logLabeledError("dataconnect", "The CloudSQL free trial has already been used on this project.");
|
|
54
|
-
utils.logLabeledError("dataconnect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
utils.logLabeledError("dataconnect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
|
|
58
|
-
const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} and update ${clc.blue("location")} in ` +
|
|
59
|
-
`${clc.green(configYamlPath)}.`;
|
|
60
|
-
utils.logLabeledError("dataconnect", reuseHint);
|
|
61
|
-
utils.logLabeledError("dataconnect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
|
|
62
|
-
}
|
|
63
|
-
exports.printFreeTrialUnavailable = printFreeTrialUnavailable;
|
|
64
39
|
function upgradeInstructions(projectId) {
|
|
65
|
-
return `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
return `To provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
|
|
41
|
+
|
|
42
|
+
1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
|
|
43
|
+
|
|
44
|
+
https://console.firebase.google.com/project/${projectId}/usage/details
|
|
45
|
+
|
|
46
|
+
2. Run ${clc.bold("firebase deploy --only dataconnect")} to deploy your Data Connect service.`;
|
|
70
47
|
}
|
|
71
48
|
exports.upgradeInstructions = upgradeInstructions;
|
|
@@ -1,95 +1,98 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUpdateReason = exports.
|
|
3
|
+
exports.getUpdateReason = exports.cloudSQLBeingCreated = exports.setupCloudSql = void 0;
|
|
4
4
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
5
5
|
const utils = require("../utils");
|
|
6
6
|
const checkIam_1 = require("./checkIam");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
|
-
const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
|
|
10
9
|
const freeTrial_1 = require("./freeTrial");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
|
|
11
|
+
async function setupCloudSql(args) {
|
|
12
|
+
await upsertInstance(Object.assign({}, args));
|
|
13
|
+
const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
|
|
14
|
+
if (requireGoogleMlIntegration && !dryRun) {
|
|
15
|
+
await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.setupCloudSql = setupCloudSql;
|
|
19
|
+
async function upsertInstance(args) {
|
|
20
|
+
const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
|
|
14
21
|
try {
|
|
15
22
|
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const why = getUpdateReason(existingInstance, enableGoogleMlIntegration);
|
|
23
|
+
utils.logLabeledBullet("dataconnect", `Found existing Cloud SQL instance ${instanceId}.`);
|
|
24
|
+
const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
|
|
19
25
|
if (why) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (dryRun) {
|
|
27
|
+
utils.logLabeledBullet("dataconnect", `Cloud SQL instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
|
|
28
|
+
`It will be updated on your next deploy.` +
|
|
29
|
+
why);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
utils.logLabeledBullet("dataconnect", `Cloud SQL instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
|
|
33
|
+
why);
|
|
34
|
+
await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, requireGoogleMlIntegration), "Updating your Cloud SQL instance...");
|
|
28
35
|
}
|
|
29
36
|
}
|
|
37
|
+
await upsertDatabase(Object.assign({}, args));
|
|
30
38
|
}
|
|
31
39
|
catch (err) {
|
|
32
40
|
if (err.status !== 404) {
|
|
33
41
|
throw err;
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}), "Creating your instance...");
|
|
54
|
-
if (newInstance) {
|
|
55
|
-
silent || utils.logLabeledBullet("dataconnect", "Instance created");
|
|
56
|
-
connectionName = (newInstance === null || newInstance === void 0 ? void 0 : newInstance.connectionName) || "";
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
silent ||
|
|
60
|
-
utils.logLabeledBullet("dataconnect", "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.");
|
|
61
|
-
return connectionName;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
43
|
+
await createInstance(Object.assign({}, args));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function createInstance(args) {
|
|
47
|
+
const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun } = args;
|
|
48
|
+
const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId);
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
utils.logLabeledBullet("dataconnect", `Cloud SQL Instance ${instanceId} not found. It will be created on your next deploy.`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await cloudSqlAdminClient.createInstance({
|
|
54
|
+
projectId,
|
|
55
|
+
location,
|
|
56
|
+
instanceId,
|
|
57
|
+
enableGoogleMlIntegration: requireGoogleMlIntegration,
|
|
58
|
+
freeTrial: !freeTrialUsed,
|
|
59
|
+
});
|
|
60
|
+
utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, !freeTrialUsed));
|
|
64
61
|
}
|
|
62
|
+
}
|
|
63
|
+
function cloudSQLBeingCreated(projectId, instanceId, includeFreeTrialToS) {
|
|
64
|
+
return (`Cloud SQL Instance ${instanceId} is being created.` +
|
|
65
|
+
(includeFreeTrialToS
|
|
66
|
+
? `\nThis instance is provided under the terms of the Data Connect no-cost trial ${(0, freeTrial_1.freeTrialTermsLink)()}`
|
|
67
|
+
: "") +
|
|
68
|
+
`
|
|
69
|
+
Meanwhile, your data are saved in a temporary database and will be migrated once complete. Monitor its progress at
|
|
70
|
+
|
|
71
|
+
${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
exports.cloudSQLBeingCreated = cloudSQLBeingCreated;
|
|
75
|
+
async function upsertDatabase(args) {
|
|
76
|
+
const { projectId, instanceId, databaseId, dryRun } = args;
|
|
65
77
|
try {
|
|
66
78
|
await cloudSqlAdminClient.getDatabase(projectId, instanceId, databaseId);
|
|
67
|
-
|
|
79
|
+
utils.logLabeledBullet("dataconnect", `Found existing Postgres Database ${databaseId}.`);
|
|
68
80
|
}
|
|
69
81
|
catch (err) {
|
|
70
|
-
if (err.status
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
|
|
78
|
-
await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
|
|
79
|
-
silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
|
|
80
|
-
}
|
|
82
|
+
if (err.status !== 404) {
|
|
83
|
+
logger_1.logger.debug(`Unexpected error from Cloud SQL: ${err}`);
|
|
84
|
+
utils.logLabeledWarning("dataconnect", `Postgres Database ${databaseId} is not accessible.`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (dryRun) {
|
|
88
|
+
utils.logLabeledBullet("dataconnect", `Postgres Database ${databaseId} not found. It will be created on your next deploy.`);
|
|
81
89
|
}
|
|
82
90
|
else {
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
|
|
92
|
+
utils.logLabeledBullet("dataconnect", `Postgres Database ${databaseId} created.`);
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
|
-
if (enableGoogleMlIntegration && !dryRun) {
|
|
88
|
-
await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
|
|
89
|
-
}
|
|
90
|
-
return connectionName;
|
|
91
95
|
}
|
|
92
|
-
exports.provisionCloudSql = provisionCloudSql;
|
|
93
96
|
function getUpdateReason(instance, requireGoogleMlIntegration) {
|
|
94
97
|
var _a, _b, _c, _d;
|
|
95
98
|
let reason = "";
|
|
@@ -112,9 +115,3 @@ function getUpdateReason(instance, requireGoogleMlIntegration) {
|
|
|
112
115
|
return reason;
|
|
113
116
|
}
|
|
114
117
|
exports.getUpdateReason = getUpdateReason;
|
|
115
|
-
function cmekWarning() {
|
|
116
|
-
const message = "Cloud SQL instances created via the Firebase CLI do not support customer managed encryption keys.\n" +
|
|
117
|
-
"If you'd like to use a CMEK to encrypt your data, first create a CMEK encrypted instance (https://cloud.google.com/sql/docs/postgres/configure-cmek#createcmekinstance).\n" +
|
|
118
|
-
"Then, edit your `dataconnect.yaml` file to use the encrypted instance and redeploy.";
|
|
119
|
-
utils.logLabeledWarning("dataconnect", message);
|
|
120
|
-
}
|