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
|
@@ -11,8 +11,13 @@ const build_1 = require("../../dataconnect/build");
|
|
|
11
11
|
const ensureApis_1 = require("../../dataconnect/ensureApis");
|
|
12
12
|
const requireTosAcceptance_1 = require("../../requireTosAcceptance");
|
|
13
13
|
const firedata_1 = require("../../gcp/firedata");
|
|
14
|
+
const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
|
|
15
|
+
const names_1 = require("../../dataconnect/names");
|
|
14
16
|
const error_1 = require("../../error");
|
|
17
|
+
const types_1 = require("../../dataconnect/types");
|
|
18
|
+
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
15
19
|
async function default_1(context, options) {
|
|
20
|
+
var _a;
|
|
16
21
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
17
22
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
18
23
|
await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
|
|
@@ -41,6 +46,37 @@ async function default_1(context, options) {
|
|
|
41
46
|
filters,
|
|
42
47
|
};
|
|
43
48
|
utils.logLabeledBullet("dataconnect", `Successfully prepared schema and connectors`);
|
|
49
|
+
if (options.dryRun) {
|
|
50
|
+
for (const si of serviceInfos) {
|
|
51
|
+
await (0, schemaMigration_1.diffSchema)(si.schema, (_a = si.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
|
|
52
|
+
}
|
|
53
|
+
utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
|
|
54
|
+
await Promise.all(serviceInfos
|
|
55
|
+
.filter((si) => {
|
|
56
|
+
return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
57
|
+
})
|
|
58
|
+
.map(async (s) => {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
|
|
61
|
+
if (postgresDatasource) {
|
|
62
|
+
const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
|
|
63
|
+
const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
|
|
64
|
+
if (!instanceId || !databaseId) {
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
|
|
68
|
+
return (0, provisionCloudSql_1.provisionCloudSql)({
|
|
69
|
+
projectId,
|
|
70
|
+
locationId: (0, names_1.parseServiceName)(s.serviceName).location,
|
|
71
|
+
instanceId,
|
|
72
|
+
databaseId,
|
|
73
|
+
enableGoogleMlIntegration,
|
|
74
|
+
waitForCreation: true,
|
|
75
|
+
dryRun: options.dryRun,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
44
80
|
logger_1.logger.debug(JSON.stringify(context.dataconnect, null, 2));
|
|
45
81
|
return;
|
|
46
82
|
}
|
|
@@ -14,14 +14,21 @@ async function default_1(context, options) {
|
|
|
14
14
|
return f.serviceId === si.dataConnectYaml.serviceId && (f.schemaOnly || f.fullService);
|
|
15
15
|
}));
|
|
16
16
|
})
|
|
17
|
-
.map((s) =>
|
|
17
|
+
.map((s) => {
|
|
18
|
+
var _a;
|
|
19
|
+
return ({
|
|
20
|
+
schema: s.schema,
|
|
21
|
+
validationMode: (_a = s.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
18
24
|
if (wantSchemas.length) {
|
|
19
25
|
utils.logLabeledBullet("dataconnect", "Releasing Data Connect schemas...");
|
|
20
26
|
for (const s of wantSchemas) {
|
|
21
27
|
await (0, schemaMigration_1.migrateSchema)({
|
|
22
28
|
options,
|
|
23
|
-
schema: s,
|
|
29
|
+
schema: s.schema,
|
|
24
30
|
validateOnly: false,
|
|
31
|
+
schemaValidation: s.validationMode,
|
|
25
32
|
});
|
|
26
33
|
}
|
|
27
34
|
utils.logLabeledBullet("dataconnect", "Schemas released.");
|
|
@@ -81,14 +81,19 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
|
|
|
81
81
|
}
|
|
82
82
|
if (payload.instancesToDelete.length) {
|
|
83
83
|
logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete));
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
if (options.dryRun) {
|
|
85
|
+
logger_1.logger.info("On your next deploy, these you will be asked if you want to delete these instances.");
|
|
86
|
+
logger_1.logger.info("If you deploy --force, they will be deleted.");
|
|
87
|
+
}
|
|
88
|
+
if (!options.dryRun &&
|
|
89
|
+
!(await prompt.confirm({
|
|
90
|
+
message: `Would you like to delete ${payload.instancesToDelete
|
|
91
|
+
.map((i) => i.instanceId)
|
|
92
|
+
.join(", ")}?`,
|
|
93
|
+
default: false,
|
|
94
|
+
nonInteractive: options.nonInteractive,
|
|
95
|
+
force: options.force,
|
|
96
|
+
}))) {
|
|
92
97
|
payload.instancesToDelete = [];
|
|
93
98
|
}
|
|
94
99
|
else {
|
|
@@ -96,7 +101,15 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
await (0, requirePermissions_1.requirePermissions)(options, permissionsNeeded);
|
|
99
|
-
|
|
104
|
+
if (options.dryRun) {
|
|
105
|
+
const appDevTos = await (0, tos_1.getAppDeveloperTOSStatus)(projectId);
|
|
106
|
+
if (!appDevTos.lastAcceptedVersion) {
|
|
107
|
+
logger_1.logger.info("On your next deploy, you will be asked to accept the Firebase Extensions App Developer Terms of Service");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
await (0, tos_1.acceptLatestAppDeveloperTOS)(options, projectId, context.want.map((i) => i.instanceId));
|
|
112
|
+
}
|
|
100
113
|
}
|
|
101
114
|
async function prepareDynamicExtensions(context, options, payload, builds) {
|
|
102
115
|
const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
|
|
@@ -5,6 +5,7 @@ const loadCJSON_1 = require("../../loadCJSON");
|
|
|
5
5
|
const rulesDeploy_1 = require("../../rulesDeploy");
|
|
6
6
|
const utils = require("../../utils");
|
|
7
7
|
const fsConfig = require("../../firestore/fsConfig");
|
|
8
|
+
const logger_1 = require("../../logger");
|
|
8
9
|
function prepareRules(context, rulesDeploy, databaseId, rulesFile) {
|
|
9
10
|
rulesDeploy.addFile(rulesFile);
|
|
10
11
|
context.firestore.rules.push({
|
|
@@ -23,6 +24,7 @@ function prepareIndexes(context, options, databaseId, indexesFileName) {
|
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
async function default_1(context, options) {
|
|
27
|
+
var _a;
|
|
26
28
|
if (options.only) {
|
|
27
29
|
const targets = options.only.split(",");
|
|
28
30
|
const excludeRules = targets.indexOf("firestore:indexes") >= 0;
|
|
@@ -57,5 +59,13 @@ async function default_1(context, options) {
|
|
|
57
59
|
if (context.firestore.rules.length > 0) {
|
|
58
60
|
await rulesDeploy.compile();
|
|
59
61
|
}
|
|
62
|
+
const rulesContext = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rules;
|
|
63
|
+
for (const ruleContext of rulesContext) {
|
|
64
|
+
const databaseId = ruleContext.databaseId;
|
|
65
|
+
const rulesFile = ruleContext.rulesFile;
|
|
66
|
+
if (!rulesFile) {
|
|
67
|
+
logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
exports.default = default_1;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const rulesDeploy_1 = require("../../rulesDeploy");
|
|
4
|
-
|
|
5
|
-
async function default_1(context, options) {
|
|
4
|
+
async function default_1(context) {
|
|
6
5
|
var _a, _b;
|
|
7
6
|
const rulesDeploy = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rulesDeploy;
|
|
8
7
|
if (!context.firestoreRules || !rulesDeploy) {
|
|
@@ -12,11 +11,9 @@ async function default_1(context, options) {
|
|
|
12
11
|
await Promise.all(rulesContext.map(async (ruleContext) => {
|
|
13
12
|
const databaseId = ruleContext.databaseId;
|
|
14
13
|
const rulesFile = ruleContext.rulesFile;
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
return;
|
|
14
|
+
if (rulesFile) {
|
|
15
|
+
return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
|
|
18
16
|
}
|
|
19
|
-
return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
|
|
20
17
|
}));
|
|
21
18
|
}
|
|
22
19
|
exports.default = default_1;
|
|
@@ -102,7 +102,7 @@ function obtainDefaultComputeServiceAgentBindings(projectNumber) {
|
|
|
102
102
|
return [runInvokerBinding, eventarcEventReceiverBinding];
|
|
103
103
|
}
|
|
104
104
|
exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
|
|
105
|
-
async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
|
|
105
|
+
async function ensureServiceAgentRoles(projectId, projectNumber, want, have, dryRun) {
|
|
106
106
|
const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
|
|
107
107
|
const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
|
|
108
108
|
const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
|
|
@@ -138,7 +138,12 @@ async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
try {
|
|
141
|
-
|
|
141
|
+
if (dryRun) {
|
|
142
|
+
logger_1.logger.info(`On your next deploy, the following required roles will be granted: ${requiredBindings.map((b) => `${b.members.join(", ")}: ${(0, colorette_1.bold)(b.role)}`)}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
|
|
146
|
+
}
|
|
142
147
|
}
|
|
143
148
|
catch (err) {
|
|
144
149
|
iam.printManualIamConfig(requiredBindings, projectId, "functions");
|
|
@@ -79,11 +79,19 @@ async function secretsToServiceAccounts(b) {
|
|
|
79
79
|
}
|
|
80
80
|
return secretsToSa;
|
|
81
81
|
}
|
|
82
|
-
async function secretAccess(projectId, wantBackend, haveBackend) {
|
|
82
|
+
async function secretAccess(projectId, wantBackend, haveBackend, dryRun) {
|
|
83
83
|
var _a, _b;
|
|
84
84
|
const ensureAccess = async (secret, serviceAccounts) => {
|
|
85
85
|
(0, utils_1.logLabeledBullet)("functions", `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.`);
|
|
86
|
-
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
const check = await (0, secretManager_1.checkServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
|
|
88
|
+
if (check.length) {
|
|
89
|
+
(0, utils_1.logLabeledBullet)("functions", `On your next deploy, ${clc.bold(serviceAccounts.join(", "))} will be granted access to secret ${clc.bold(secret)}.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
|
|
94
|
+
}
|
|
87
95
|
(0, utils_1.logLabeledSuccess)("functions", `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.`);
|
|
88
96
|
};
|
|
89
97
|
const wantSecrets = await secretsToServiceAccounts(wantBackend);
|
|
@@ -183,9 +183,9 @@ async function prepare(context, options, payload) {
|
|
|
183
183
|
await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
|
|
184
184
|
await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
|
|
185
185
|
await backend.checkAvailability(context, matchingBackend);
|
|
186
|
-
await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend);
|
|
187
186
|
await validate.secretsAreValid(projectId, matchingBackend);
|
|
188
|
-
await
|
|
187
|
+
await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend, options.dryRun);
|
|
188
|
+
await ensure.secretAccess(projectId, matchingBackend, haveBackend, options.dryRun);
|
|
189
189
|
updateEndpointTargetedStatus(wantBackends, context.filters || []);
|
|
190
190
|
(0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
|
|
191
191
|
}
|
package/lib/deploy/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const chain = async function (fns, context, options, payload) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
const deploy = async function (targetNames, options, customContext = {}) {
|
|
44
|
+
var _a;
|
|
44
45
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
45
46
|
const payload = {};
|
|
46
47
|
const context = Object.assign({ projectId }, customContext);
|
|
@@ -85,9 +86,11 @@ const deploy = async function (targetNames, options, customContext = {}) {
|
|
|
85
86
|
}
|
|
86
87
|
predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
|
|
87
88
|
prepares.push(target.prepare);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if (!options.dryRun) {
|
|
90
|
+
deploys.push(target.deploy);
|
|
91
|
+
releases.push(target.release);
|
|
92
|
+
postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
95
|
logger_1.logger.info();
|
|
93
96
|
logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
|
|
@@ -110,11 +113,12 @@ const deploy = async function (targetNames, options, customContext = {}) {
|
|
|
110
113
|
analyticsParams[t] = "true";
|
|
111
114
|
}
|
|
112
115
|
await (0, track_1.trackGA4)("product_deploy", analyticsParams, duration);
|
|
116
|
+
const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
|
|
113
117
|
logger_1.logger.info();
|
|
114
|
-
(0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(
|
|
118
|
+
(0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
|
|
115
119
|
logger_1.logger.info();
|
|
116
120
|
const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
|
|
117
|
-
logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)(options.project, "/overview"));
|
|
121
|
+
logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_a = options.project) !== null && _a !== void 0 ? _a : "_", "/overview"));
|
|
118
122
|
if (deployedHosting) {
|
|
119
123
|
(0, lodash_1.each)(context.hosting.deploys, (deploy) => {
|
|
120
124
|
logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
|
|
@@ -46,20 +46,20 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
46
46
|
},
|
|
47
47
|
dataconnect: process.platform === "darwin"
|
|
48
48
|
? {
|
|
49
|
-
version: "1.3.
|
|
50
|
-
expectedSize:
|
|
51
|
-
expectedChecksum: "
|
|
49
|
+
version: "1.3.7",
|
|
50
|
+
expectedSize: 25019136,
|
|
51
|
+
expectedChecksum: "648ca3e289db8209c2d555bb381e5e5e",
|
|
52
52
|
}
|
|
53
53
|
: process.platform === "win32"
|
|
54
54
|
? {
|
|
55
|
-
version: "1.3.
|
|
56
|
-
expectedSize:
|
|
57
|
-
expectedChecksum: "
|
|
55
|
+
version: "1.3.7",
|
|
56
|
+
expectedSize: 25441792,
|
|
57
|
+
expectedChecksum: "16081147aa94f1b8691329d5b9430b69",
|
|
58
58
|
}
|
|
59
59
|
: {
|
|
60
|
-
version: "1.3.
|
|
61
|
-
expectedSize:
|
|
62
|
-
expectedChecksum: "
|
|
60
|
+
version: "1.3.7",
|
|
61
|
+
expectedSize: 24928408,
|
|
62
|
+
expectedChecksum: "8749930f3a43f616e3ae231af8c787a8",
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
exports.DownloadDetails = {
|
|
@@ -4,14 +4,21 @@ exports.checkDatabaseType = void 0;
|
|
|
4
4
|
const api_1 = require("../api");
|
|
5
5
|
const apiv2_1 = require("../apiv2");
|
|
6
6
|
const logger_1 = require("../logger");
|
|
7
|
-
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
async function checkDatabaseType(projectId, databaseId = "(default)") {
|
|
8
9
|
try {
|
|
9
10
|
const client = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
|
|
10
|
-
const resp = await client.get(`/projects/${projectId}/databases
|
|
11
|
+
const resp = await client.get(`/projects/${projectId}/databases/${databaseId}`);
|
|
11
12
|
return resp.body.type;
|
|
12
13
|
}
|
|
13
14
|
catch (err) {
|
|
14
|
-
logger_1.logger.debug("error getting database type", err);
|
|
15
|
+
logger_1.logger.debug("error getting database type: ", err);
|
|
16
|
+
if (err instanceof error_1.FirebaseError) {
|
|
17
|
+
if (err.status === 404) {
|
|
18
|
+
logger_1.logger.info(`${databaseId} does not exist in project ${projectId}.`);
|
|
19
|
+
return "DATABASE_DOES_NOT_EXIST";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
15
22
|
return undefined;
|
|
16
23
|
}
|
|
17
24
|
}
|
|
@@ -15,7 +15,7 @@ exports.support = "preview";
|
|
|
15
15
|
exports.type = 3;
|
|
16
16
|
exports.docsUrl = "https://firebase.google.com/docs/hosting/frameworks/angular";
|
|
17
17
|
const DEFAULT_BUILD_SCRIPT = ["ng build"];
|
|
18
|
-
exports.supportedRange = "
|
|
18
|
+
exports.supportedRange = "16 - 18";
|
|
19
19
|
async function discover(dir) {
|
|
20
20
|
if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
|
|
21
21
|
return;
|
|
@@ -181,9 +181,21 @@ exports.handle = function(req,res) {
|
|
|
181
181
|
};\n`;
|
|
182
182
|
}
|
|
183
183
|
else if (serverOutputPath) {
|
|
184
|
-
bootstrapScript = `
|
|
184
|
+
bootstrapScript = `
|
|
185
|
+
const app = new Promise((resolve) => {
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
const port = process.env.PORT;
|
|
188
|
+
const socket = 'express.sock';
|
|
189
|
+
process.env.PORT = socket;
|
|
190
|
+
|
|
191
|
+
${(serverEntry === null || serverEntry === void 0 ? void 0 : serverEntry.endsWith(".mjs"))
|
|
185
192
|
? `import(\`./${serverOutputPath}/${serverEntry}\`)`
|
|
186
|
-
: `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(
|
|
193
|
+
: `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(({ app }) => {
|
|
194
|
+
process.env.PORT = port;
|
|
195
|
+
resolve(app());
|
|
196
|
+
});
|
|
197
|
+
}, 0);
|
|
198
|
+
});
|
|
187
199
|
exports.handle = (req,res) => app.then(it => it(req,res));\n`;
|
|
188
200
|
}
|
|
189
201
|
else {
|
|
@@ -265,7 +265,7 @@ exports.findEsbuildPath = findEsbuildPath;
|
|
|
265
265
|
function getGlobalEsbuildVersion(binPath) {
|
|
266
266
|
var _a;
|
|
267
267
|
try {
|
|
268
|
-
const versionOutput = (_a = (0, child_process_1.execSync)(
|
|
268
|
+
const versionOutput = (_a = (0, child_process_1.execSync)(`"${binPath}" --version`, { encoding: "utf8" })) === null || _a === void 0 ? void 0 : _a.trim();
|
|
269
269
|
if (!versionOutput) {
|
|
270
270
|
return null;
|
|
271
271
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
|
|
3
|
+
exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.fdcSqlRoleMap = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
|
|
4
4
|
const projectUtils_1 = require("../../projectUtils");
|
|
5
5
|
const connect_1 = require("./connect");
|
|
6
6
|
const iam_1 = require("../iam");
|
|
@@ -19,6 +19,11 @@ function firebasewriter(databaseId) {
|
|
|
19
19
|
return `firebasewriter_${databaseId}_public`;
|
|
20
20
|
}
|
|
21
21
|
exports.firebasewriter = firebasewriter;
|
|
22
|
+
exports.fdcSqlRoleMap = {
|
|
23
|
+
owner: firebaseowner,
|
|
24
|
+
writer: firebasewriter,
|
|
25
|
+
reader: firebasereader,
|
|
26
|
+
};
|
|
22
27
|
async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRole, granteeRole) {
|
|
23
28
|
const checkCmd = `
|
|
24
29
|
DO $$
|
package/lib/gcp/secretManager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.labels = exports.ensureApi = exports.isAppHostingManaged = exports.isFunctionsManaged = exports.FIREBASE_MANAGED = exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.getSecretMetadata = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
|
|
3
|
+
exports.labels = exports.ensureApi = exports.isAppHostingManaged = exports.isFunctionsManaged = exports.FIREBASE_MANAGED = exports.checkServiceAgentRole = exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.getSecretMetadata = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
@@ -207,6 +207,14 @@ async function setIamPolicy(secret, bindings) {
|
|
|
207
207
|
}
|
|
208
208
|
exports.setIamPolicy = setIamPolicy;
|
|
209
209
|
async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
|
|
210
|
+
const bindings = await checkServiceAgentRole(secret, serviceAccountEmails, role);
|
|
211
|
+
if (bindings.length) {
|
|
212
|
+
await module.exports.setIamPolicy(secret, bindings);
|
|
213
|
+
}
|
|
214
|
+
(0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
exports.ensureServiceAgentRole = ensureServiceAgentRole;
|
|
217
|
+
async function checkServiceAgentRole(secret, serviceAccountEmails, role) {
|
|
210
218
|
const policy = await module.exports.getIamPolicy(secret);
|
|
211
219
|
const bindings = policy.bindings || [];
|
|
212
220
|
let binding = bindings.find((b) => b.role === role);
|
|
@@ -222,11 +230,10 @@ async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
|
|
|
222
230
|
}
|
|
223
231
|
}
|
|
224
232
|
if (shouldShortCircuit)
|
|
225
|
-
return;
|
|
226
|
-
|
|
227
|
-
(0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
|
|
233
|
+
return [];
|
|
234
|
+
return bindings;
|
|
228
235
|
}
|
|
229
|
-
exports.
|
|
236
|
+
exports.checkServiceAgentRole = checkServiceAgentRole;
|
|
230
237
|
exports.FIREBASE_MANAGED = "firebase-managed";
|
|
231
238
|
function isFunctionsManaged(secret) {
|
|
232
239
|
return (secret.labels[exports.FIREBASE_MANAGED] === "true" || secret.labels[exports.FIREBASE_MANAGED] === "functions");
|
|
@@ -8,13 +8,16 @@ const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
|
|
|
8
8
|
const freeTrial_1 = require("../../../dataconnect/freeTrial");
|
|
9
9
|
const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
|
|
10
10
|
const ensureApis_1 = require("../../../dataconnect/ensureApis");
|
|
11
|
+
const experiments = require("../../../experiments");
|
|
11
12
|
const client_1 = require("../../../dataconnect/client");
|
|
12
13
|
const emulators_1 = require("../emulators");
|
|
13
14
|
const names_1 = require("../../../dataconnect/names");
|
|
14
15
|
const logger_1 = require("../../../logger");
|
|
15
16
|
const templates_1 = require("../../../templates");
|
|
16
17
|
const utils_1 = require("../../../utils");
|
|
18
|
+
const cloudbilling_1 = require("../../../gcp/cloudbilling");
|
|
17
19
|
const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
|
|
20
|
+
const DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect-fdccompatiblemode.yaml");
|
|
18
21
|
const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
|
|
19
22
|
const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
|
|
20
23
|
const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
|
|
@@ -53,7 +56,8 @@ async function askQuestions(setup, config) {
|
|
|
53
56
|
schemaGql: [],
|
|
54
57
|
shouldProvisionCSQL: false,
|
|
55
58
|
};
|
|
56
|
-
|
|
59
|
+
const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
|
|
60
|
+
info = await promptForService(setup, info, isBillingEnabled);
|
|
57
61
|
if (info.cloudSqlInstanceId === "") {
|
|
58
62
|
info = await promptForCloudSQLInstance(setup, info);
|
|
59
63
|
}
|
|
@@ -70,8 +74,9 @@ async function askQuestions(setup, config) {
|
|
|
70
74
|
setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
|
|
71
75
|
info.shouldProvisionCSQL = !!(setup.projectId &&
|
|
72
76
|
(info.isNewInstance || info.isNewDatabase) &&
|
|
77
|
+
isBillingEnabled &&
|
|
73
78
|
(await (0, prompt_1.confirm)({
|
|
74
|
-
message:
|
|
79
|
+
message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
|
|
75
80
|
default: true,
|
|
76
81
|
})));
|
|
77
82
|
return info;
|
|
@@ -125,7 +130,9 @@ function subDataconnectYamlValues(replacementValues) {
|
|
|
125
130
|
connectorDirs: "__connectorDirs__",
|
|
126
131
|
locationId: "__location__",
|
|
127
132
|
};
|
|
128
|
-
let replaced =
|
|
133
|
+
let replaced = experiments.isEnabled("fdccompatiblemode")
|
|
134
|
+
? DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE
|
|
135
|
+
: DATACONNECT_YAML_TEMPLATE;
|
|
129
136
|
for (const [k, v] of Object.entries(replacementValues)) {
|
|
130
137
|
replaced = replaced.replace(replacements[k], JSON.stringify(v));
|
|
131
138
|
}
|
|
@@ -141,10 +148,15 @@ function subConnectorYamlValues(replacementValues) {
|
|
|
141
148
|
}
|
|
142
149
|
return replaced;
|
|
143
150
|
}
|
|
144
|
-
async function promptForService(setup, info) {
|
|
145
|
-
var _a, _b, _c
|
|
151
|
+
async function promptForService(setup, info, isBillingEnabled) {
|
|
152
|
+
var _a, _b, _c;
|
|
146
153
|
if (setup.projectId) {
|
|
147
|
-
|
|
154
|
+
if (isBillingEnabled) {
|
|
155
|
+
await (0, ensureApis_1.ensureApis)(setup.projectId);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
|
|
159
|
+
}
|
|
148
160
|
const existingServices = await (0, client_1.listAllServices)(setup.projectId);
|
|
149
161
|
const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
|
|
150
162
|
return {
|
|
@@ -171,14 +183,15 @@ async function promptForService(setup, info) {
|
|
|
171
183
|
info.serviceId = serviceName.serviceId;
|
|
172
184
|
info.locationId = serviceName.location;
|
|
173
185
|
if (choice.schema) {
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
|
|
187
|
+
if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
|
|
188
|
+
const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
|
|
176
189
|
info.cloudSqlInstanceId = instanceName.instanceId;
|
|
177
190
|
}
|
|
178
191
|
if (choice.schema.source.files) {
|
|
179
192
|
info.schemaGql = choice.schema.source.files;
|
|
180
193
|
}
|
|
181
|
-
info.cloudSqlDatabase = (
|
|
194
|
+
info.cloudSqlDatabase = (_c = (_b = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database) !== null && _c !== void 0 ? _c : "";
|
|
182
195
|
const connectors = await (0, client_1.listConnectors)(choice.service.name, [
|
|
183
196
|
"connectors.name",
|
|
184
197
|
"connectors.source.files",
|
|
@@ -201,7 +214,7 @@ async function promptForService(setup, info) {
|
|
|
201
214
|
info.serviceId = await (0, prompt_1.promptOnce)({
|
|
202
215
|
message: "What ID would you like to use for this service?",
|
|
203
216
|
type: "input",
|
|
204
|
-
default: "
|
|
217
|
+
default: "app",
|
|
205
218
|
});
|
|
206
219
|
}
|
|
207
220
|
return info;
|
|
@@ -233,7 +246,7 @@ async function promptForCloudSQLInstance(setup, info) {
|
|
|
233
246
|
info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
234
247
|
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
235
248
|
type: "input",
|
|
236
|
-
default:
|
|
249
|
+
default: `${info.serviceId || "app"}-fdc`,
|
|
237
250
|
});
|
|
238
251
|
}
|
|
239
252
|
if (info.locationId === "") {
|
|
@@ -9,13 +9,14 @@ const rules = require("./rules");
|
|
|
9
9
|
const indexes = require("./indexes");
|
|
10
10
|
const error_1 = require("../../../error");
|
|
11
11
|
const clc = require("colorette");
|
|
12
|
+
const prompt_1 = require("../../../prompt");
|
|
12
13
|
async function checkProjectSetup(setup, config, options) {
|
|
13
14
|
const firestoreUnusedError = new error_1.FirebaseError(`It looks like you haven't used Cloud Firestore in this project before. Go to ${clc.bold(clc.underline(`https://console.firebase.google.com/project/${setup.projectId}/firestore`))} to create your Cloud Firestore database.`, { exit: 1 });
|
|
14
15
|
const isFirestoreEnabled = await apiEnabled.check(setup.projectId, "firestore.googleapis.com", "", true);
|
|
15
16
|
if (!isFirestoreEnabled) {
|
|
16
17
|
throw firestoreUnusedError;
|
|
17
18
|
}
|
|
18
|
-
const dbType = await (
|
|
19
|
+
const dbType = await getDatabaseType(setup);
|
|
19
20
|
logger_1.logger.debug(`database_type: ${dbType}`);
|
|
20
21
|
if (!dbType) {
|
|
21
22
|
throw firestoreUnusedError;
|
|
@@ -25,6 +26,24 @@ async function checkProjectSetup(setup, config, options) {
|
|
|
25
26
|
}
|
|
26
27
|
await (0, requirePermissions_1.requirePermissions)(Object.assign(Object.assign({}, options), { project: setup.projectId }));
|
|
27
28
|
}
|
|
29
|
+
async function getDatabaseType(setup) {
|
|
30
|
+
const dbType = await (0, checkDatabaseType_1.checkDatabaseType)(setup.projectId, setup.databaseId);
|
|
31
|
+
logger_1.logger.debug(`database_type: ${dbType}`);
|
|
32
|
+
if (dbType === "DATABASE_DOES_NOT_EXIST") {
|
|
33
|
+
setup.databaseId = await selectDatabaseByPrompting();
|
|
34
|
+
return await getDatabaseType(setup);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return dbType;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function selectDatabaseByPrompting() {
|
|
41
|
+
const database = await (0, prompt_1.promptOnce)({
|
|
42
|
+
type: "input",
|
|
43
|
+
message: "Please input the name of the Native Firestore database you would like to use:",
|
|
44
|
+
});
|
|
45
|
+
return database;
|
|
46
|
+
}
|
|
28
47
|
async function doSetup(setup, config, options) {
|
|
29
48
|
if (setup.projectId) {
|
|
30
49
|
await checkProjectSetup(setup, config, options);
|
|
@@ -47,15 +47,15 @@ function initIndexes(setup, config) {
|
|
|
47
47
|
if (!setup.projectId) {
|
|
48
48
|
return config.writeProjectFile(setup.config.firestore.indexes, INDEXES_TEMPLATE);
|
|
49
49
|
}
|
|
50
|
-
return getIndexesFromConsole(setup.projectId).then((contents) => {
|
|
50
|
+
return getIndexesFromConsole(setup.projectId, setup.databaseId).then((contents) => {
|
|
51
51
|
return config.writeProjectFile(setup.config.firestore.indexes, contents);
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
exports.initIndexes = initIndexes;
|
|
56
|
-
function getIndexesFromConsole(projectId) {
|
|
57
|
-
const indexesPromise = indexes.listIndexes(projectId);
|
|
58
|
-
const fieldOverridesPromise = indexes.listFieldOverrides(projectId);
|
|
56
|
+
function getIndexesFromConsole(projectId, databaseId) {
|
|
57
|
+
const indexesPromise = indexes.listIndexes(projectId, databaseId);
|
|
58
|
+
const fieldOverridesPromise = indexes.listFieldOverrides(projectId, databaseId);
|
|
59
59
|
return Promise.all([indexesPromise, fieldOverridesPromise])
|
|
60
60
|
.then((res) => {
|
|
61
61
|
return indexes.makeIndexSpec(res[0], res[1]);
|