firebase-tools 15.19.1 → 15.20.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/appdistribution/client.js +6 -5
- package/lib/appdistribution/options-parser-util.js +21 -0
- package/lib/commands/appdistribution-distribute.js +13 -4
- package/lib/commands/apptesting.js +9 -3
- package/lib/crashlytics/sourcemap.js +1 -1
- package/lib/deploy/dataconnect/context.js +0 -2
- package/lib/deploy/dataconnect/deploy.js +0 -19
- package/lib/deploy/functions/prepare.js +8 -104
- package/lib/deploy/functions/services/ailogic.js +17 -10
- package/lib/deploy/functions/services/auth.js +3 -0
- package/lib/deploy/functions/services/database.js +18 -0
- package/lib/deploy/functions/services/dataconnect.js +20 -0
- package/lib/deploy/functions/services/firestore.js +12 -0
- package/lib/deploy/functions/services/index.js +18 -7
- package/lib/deploy/functions/services/storage.js +14 -0
- package/lib/deploy/functions/triggerRegionHelper.js +2 -4
- package/lib/gcp/location.js +16 -1
- package/lib/init/features/dataconnect/create_app.js +3 -4
- package/lib/init/features/dataconnect/sdk.js +2 -2
- package/lib/mcp/tools/apptesting/tests.js +3 -1
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -225,17 +225,18 @@ class AppDistributionClient {
|
|
|
225
225
|
}
|
|
226
226
|
utils.logSuccess(`Testers removed from group successfully`);
|
|
227
227
|
}
|
|
228
|
-
async createReleaseTest(releaseName, devices,
|
|
228
|
+
async createReleaseTest(releaseName, devices, options = {}) {
|
|
229
229
|
try {
|
|
230
230
|
const response = await this.appDistroV1AlphaClient.request({
|
|
231
231
|
method: "POST",
|
|
232
232
|
path: `${releaseName}/tests`,
|
|
233
233
|
body: {
|
|
234
234
|
deviceExecutions: devices.map((device) => ({ device })),
|
|
235
|
-
loginCredential,
|
|
236
|
-
testCase: testCaseName,
|
|
237
|
-
aiInstructions: aiInstructions,
|
|
238
|
-
displayName: displayName,
|
|
235
|
+
loginCredential: options.loginCredential,
|
|
236
|
+
testCase: options.testCaseName,
|
|
237
|
+
aiInstructions: options.aiInstructions,
|
|
238
|
+
displayName: options.displayName,
|
|
239
|
+
resultsBucket: options.resultsBucket,
|
|
239
240
|
},
|
|
240
241
|
});
|
|
241
242
|
return response.body;
|
|
@@ -8,6 +8,7 @@ exports.getAppName = getAppName;
|
|
|
8
8
|
exports.toAppName = toAppName;
|
|
9
9
|
exports.parseTestDevices = parseTestDevices;
|
|
10
10
|
exports.getLoginCredential = getLoginCredential;
|
|
11
|
+
exports.getResultsBucket = getResultsBucket;
|
|
11
12
|
const fs = require("fs-extra");
|
|
12
13
|
const error_1 = require("../error");
|
|
13
14
|
const projectUtils_1 = require("../projectUtils");
|
|
@@ -137,3 +138,23 @@ function getLoginCredential(args) {
|
|
|
137
138
|
function isPresenceMismatched(value1, value2) {
|
|
138
139
|
return (value1 && !value2) || (!value1 && value2);
|
|
139
140
|
}
|
|
141
|
+
const APP_NAME_REGEX = /^projects\/(?<projectNumber>[^\/]+)\/apps\/(?<appId>[^\/]+)$/;
|
|
142
|
+
const BUCKET_NAME_FORMAT_REGEX = /^[a-z0-9_.-]+$/;
|
|
143
|
+
function getResultsBucket(bucket, appName) {
|
|
144
|
+
if (!bucket) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
let bucketName = bucket;
|
|
148
|
+
if (bucketName.startsWith("gs://")) {
|
|
149
|
+
bucketName = bucketName.substring(5);
|
|
150
|
+
}
|
|
151
|
+
if (!BUCKET_NAME_FORMAT_REGEX.test(bucketName)) {
|
|
152
|
+
throw new error_1.FirebaseError(`Invalid results bucket format: ${bucket}`);
|
|
153
|
+
}
|
|
154
|
+
const match = APP_NAME_REGEX.exec(appName);
|
|
155
|
+
if (!match || typeof match.groups === "undefined") {
|
|
156
|
+
throw new error_1.FirebaseError(`Invalid app name: ${appName}`);
|
|
157
|
+
}
|
|
158
|
+
const { projectNumber } = match.groups;
|
|
159
|
+
return `projects/${projectNumber}/buckets/${bucketName}`;
|
|
160
|
+
}
|
|
@@ -39,9 +39,11 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
39
39
|
.option("--test-non-blocking", "run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
|
|
40
40
|
.option("--test-case-ids <string>", "a comma-separated list of test case IDs.")
|
|
41
41
|
.option("--test-case-ids-file <file>", "path to file with a comma- or newline-separated list of test case IDs.")
|
|
42
|
+
.option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw results of any automated tests will be stored. If this flag is not set, Firebase creates a bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
|
|
42
43
|
.before(requireAuth_1.requireAuth)
|
|
43
44
|
.action(async (file, options) => {
|
|
44
45
|
const appName = (0, options_parser_util_1.getAppName)(options);
|
|
46
|
+
const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
|
|
45
47
|
const distribution = new distribution_1.Distribution(file);
|
|
46
48
|
const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
|
|
47
49
|
const testers = (0, options_parser_util_1.parseIntoStringArray)(options.testers, options.testersFile);
|
|
@@ -58,9 +60,9 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
58
60
|
usernameResourceName: options.testUsernameResource,
|
|
59
61
|
passwordResourceName: options.testPasswordResource,
|
|
60
62
|
});
|
|
61
|
-
await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential);
|
|
63
|
+
await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential, resultsBucket);
|
|
62
64
|
});
|
|
63
|
-
async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential) {
|
|
65
|
+
async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential, resultsBucket) {
|
|
64
66
|
const requests = new client_1.AppDistributionClient();
|
|
65
67
|
let aabInfo;
|
|
66
68
|
if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
|
|
@@ -116,11 +118,18 @@ async function distribute(appName, distribution, testCases, testDevices, release
|
|
|
116
118
|
utils.logBullet("starting automated test (note: this feature is in beta)");
|
|
117
119
|
const releaseTestPromises = [];
|
|
118
120
|
if (!testCases.length) {
|
|
119
|
-
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices,
|
|
121
|
+
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
|
|
122
|
+
loginCredential,
|
|
123
|
+
resultsBucket,
|
|
124
|
+
}));
|
|
120
125
|
}
|
|
121
126
|
else {
|
|
122
127
|
for (const testCaseId of testCases) {
|
|
123
|
-
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices,
|
|
128
|
+
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
|
|
129
|
+
loginCredential,
|
|
130
|
+
testCaseName: `${appName}/testCases/${testCaseId}`,
|
|
131
|
+
resultsBucket,
|
|
132
|
+
}));
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
const releaseTests = await Promise.all(releaseTestPromises);
|
|
@@ -29,9 +29,11 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
29
29
|
.option("--test-devices <string>", "Semicolon-separated list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
30
30
|
.option("--test-devices-file <string>", "Path to file containing a list of semicolon- or newline-separated devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
31
31
|
.option("--test-non-blocking", "Run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
|
|
32
|
+
.option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw test results will be stored. If this flag is not set, Firebase creates a default bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
|
|
32
33
|
.before(requireAuth_1.requireAuth)
|
|
33
34
|
.action(async (target, options) => {
|
|
34
35
|
const appName = (0, options_parser_util_1.getAppName)(options);
|
|
36
|
+
const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
|
|
35
37
|
const testDir = path.resolve(options.testDir || "tests");
|
|
36
38
|
if (!(0, fsutils_1.dirExistsSync)(testDir)) {
|
|
37
39
|
throw new error_1.FirebaseError(`Tests directory not found: ${testDir}. Use the --test-dir flag to choose a different directory.`);
|
|
@@ -60,7 +62,7 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
60
62
|
utils.logBullet(`Using release ${release.displayVersion} created at ${release.createTime}`);
|
|
61
63
|
}
|
|
62
64
|
invokeSpinner.start();
|
|
63
|
-
releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices);
|
|
65
|
+
releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices, resultsBucket);
|
|
64
66
|
invokeSpinner.text = `${(0, parseTestFiles_1.pluralizeTests)(releaseTests.length)} started successfully!`;
|
|
65
67
|
invokeSpinner.succeed();
|
|
66
68
|
}
|
|
@@ -76,14 +78,18 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
76
78
|
utils.logBullet(`View detailed results in the Firebase Console:\n${release.firebaseConsoleUri}`);
|
|
77
79
|
}
|
|
78
80
|
});
|
|
79
|
-
async function invokeTests(client, releaseName, testDefs, devices) {
|
|
81
|
+
async function invokeTests(client, releaseName, testDefs, devices, resultsBucket) {
|
|
80
82
|
try {
|
|
81
83
|
const releaseTests = [];
|
|
82
84
|
for (const testDef of testDefs) {
|
|
83
85
|
const aiInstructions = {
|
|
84
86
|
steps: testDef.testCase.steps,
|
|
85
87
|
};
|
|
86
|
-
releaseTests.push(await client.createReleaseTest(releaseName, devices,
|
|
88
|
+
releaseTests.push(await client.createReleaseTest(releaseName, devices, {
|
|
89
|
+
aiInstructions,
|
|
90
|
+
displayName: testDef.testCase.displayName,
|
|
91
|
+
resultsBucket,
|
|
92
|
+
}));
|
|
87
93
|
}
|
|
88
94
|
return releaseTests;
|
|
89
95
|
}
|
|
@@ -253,7 +253,7 @@ async function registerSourceMap(sourceMap) {
|
|
|
253
253
|
const client = new apiv2_1.Client({
|
|
254
254
|
urlPrefix: "https://firebasetelemetryadmin.googleapis.com",
|
|
255
255
|
auth: true,
|
|
256
|
-
apiVersion: "
|
|
256
|
+
apiVersion: "v1alpha",
|
|
257
257
|
});
|
|
258
258
|
try {
|
|
259
259
|
await client.patch(sourceMap.name, sourceMap, { queryParams: { allowMissing: "true" } });
|
|
@@ -7,7 +7,6 @@ function initDeployStats() {
|
|
|
7
7
|
numBuildErrors: 0,
|
|
8
8
|
numBuildWarnings: new Map(),
|
|
9
9
|
numServiceCreated: 0,
|
|
10
|
-
numServiceDeleted: 0,
|
|
11
10
|
numSchemaMigrated: 0,
|
|
12
11
|
numConnectorUpdatedBeforeSchema: 0,
|
|
13
12
|
numConnectorUpdatedAfterSchema: 0,
|
|
@@ -24,7 +23,6 @@ function deployStatsParams(stats) {
|
|
|
24
23
|
return {
|
|
25
24
|
missing_billing: (!!stats.missingBilling).toString(),
|
|
26
25
|
num_service_created: stats.numServiceCreated,
|
|
27
|
-
num_service_deleted: stats.numServiceDeleted,
|
|
28
26
|
num_schema_migrated: stats.numSchemaMigrated,
|
|
29
27
|
num_connector_updated_before_schema: stats.numConnectorUpdatedBeforeSchema,
|
|
30
28
|
num_connector_updated_after_schema: stats.numConnectorUpdatedAfterSchema,
|
|
@@ -9,7 +9,6 @@ const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
|
|
|
9
9
|
const names_1 = require("../../dataconnect/names");
|
|
10
10
|
const api_1 = require("../../api");
|
|
11
11
|
const ensureApiEnabled = require("../../ensureApiEnabled");
|
|
12
|
-
const prompt_1 = require("../../prompt");
|
|
13
12
|
async function default_1(context, options) {
|
|
14
13
|
const dataconnect = context.dataconnect;
|
|
15
14
|
if (!dataconnect) {
|
|
@@ -31,29 +30,11 @@ async function default_1(context, options) {
|
|
|
31
30
|
filters?.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
32
31
|
});
|
|
33
32
|
dataconnect.deployStats.numServiceCreated = servicesToCreate.length;
|
|
34
|
-
const servicesToDelete = filters
|
|
35
|
-
? []
|
|
36
|
-
: services.filter((s) => !serviceInfos.some((si) => matches(si, s)));
|
|
37
|
-
dataconnect.deployStats.numServiceDeleted = servicesToDelete.length;
|
|
38
33
|
await Promise.all(servicesToCreate.map(async (s) => {
|
|
39
34
|
const { projectId, locationId, serviceId } = splitName(s.serviceName);
|
|
40
35
|
await client.createService(projectId, locationId, serviceId);
|
|
41
36
|
utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
|
|
42
37
|
}));
|
|
43
|
-
if (servicesToDelete.length) {
|
|
44
|
-
const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
|
|
45
|
-
if (await (0, prompt_1.confirm)({
|
|
46
|
-
force: false,
|
|
47
|
-
nonInteractive: options.nonInteractive,
|
|
48
|
-
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
|
|
49
|
-
default: false,
|
|
50
|
-
})) {
|
|
51
|
-
await Promise.all(servicesToDelete.map(async (s) => {
|
|
52
|
-
await client.deleteService(s.name);
|
|
53
|
-
utils.logLabeledSuccess("dataconnect", `Deleted service ${s.name}`);
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
38
|
utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
|
|
58
39
|
await Promise.all(serviceInfos
|
|
59
40
|
.filter((si) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.EVENTARC_SOURCE_ENV = void 0;
|
|
4
4
|
exports.prepare = prepare;
|
|
5
5
|
exports.resolveDefaultRegionsForBuild = resolveDefaultRegionsForBuild;
|
|
6
6
|
exports.inferDetailsFromExisting = inferDetailsFromExisting;
|
|
@@ -23,12 +23,7 @@ const runtimes = require("./runtimes");
|
|
|
23
23
|
const supported = require("./runtimes/supported");
|
|
24
24
|
const validate = require("./validate");
|
|
25
25
|
const ensure = require("./ensure");
|
|
26
|
-
const
|
|
27
|
-
const firestore_1 = require("./services/firestore");
|
|
28
|
-
const storage_1 = require("./services/storage");
|
|
29
|
-
const database_1 = require("./services/database");
|
|
30
|
-
const ailogic_1 = require("./services/ailogic");
|
|
31
|
-
const names_1 = require("../../dataconnect/names");
|
|
26
|
+
const services_1 = require("./services");
|
|
32
27
|
const api_1 = require("../../api");
|
|
33
28
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
34
29
|
const utils_1 = require("../../utils");
|
|
@@ -49,7 +44,6 @@ const functional_1 = require("../../functional");
|
|
|
49
44
|
const prepare_1 = require("../extensions/prepare");
|
|
50
45
|
const prompt = require("../../prompt");
|
|
51
46
|
exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
|
|
52
|
-
exports.DEFAULT_FUNCTION_REGION = "us-central1";
|
|
53
47
|
async function prepare(context, options, payload) {
|
|
54
48
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
55
49
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
@@ -234,7 +228,7 @@ async function prepare(context, options, payload) {
|
|
|
234
228
|
async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
235
229
|
for (const [id, endpoint] of Object.entries(buildObj.endpoints)) {
|
|
236
230
|
if (!endpoint.region?.length || endpoint.region.includes(build.REGION_TBD)) {
|
|
237
|
-
let resolvedRegion =
|
|
231
|
+
let resolvedRegion = services_1.FALLBACK_DEPLOYMENT_REGION;
|
|
238
232
|
let matching;
|
|
239
233
|
for (const region of Object.keys(have.endpoints)) {
|
|
240
234
|
if (have.endpoints[region][id]) {
|
|
@@ -249,109 +243,19 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
|
249
243
|
}
|
|
250
244
|
else {
|
|
251
245
|
try {
|
|
252
|
-
|
|
253
|
-
resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
|
|
254
|
-
}
|
|
255
|
-
else if (build.isEventTriggered(endpoint)) {
|
|
256
|
-
resolvedRegion = await resolveRegionForEventTrigger(endpoint.project, endpoint.eventTrigger);
|
|
257
|
-
}
|
|
246
|
+
resolvedRegion = await resolveRegionForTrigger(endpoint);
|
|
258
247
|
}
|
|
259
248
|
catch (err) {
|
|
260
|
-
logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${
|
|
249
|
+
logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${services_1.FALLBACK_DEPLOYMENT_REGION}.`, (0, error_1.getErrStack)(err));
|
|
261
250
|
}
|
|
262
251
|
}
|
|
263
252
|
endpoint.region = [resolvedRegion];
|
|
264
253
|
}
|
|
265
254
|
}
|
|
266
255
|
}
|
|
267
|
-
function
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
return "us-east1";
|
|
271
|
-
}
|
|
272
|
-
if ((0, ailogic_1.isGlobalAILogicTrigger)(blockingTrigger)) {
|
|
273
|
-
return "us-east1";
|
|
274
|
-
}
|
|
275
|
-
return exports.DEFAULT_FUNCTION_REGION;
|
|
276
|
-
}
|
|
277
|
-
async function resolveRegionForEventTrigger(project, eventTrigger) {
|
|
278
|
-
const eventType = eventTrigger.eventType;
|
|
279
|
-
if (eventType.startsWith("google.cloud.pubsub.") ||
|
|
280
|
-
eventType.startsWith("providers/cloud.auth/eventTypes/") ||
|
|
281
|
-
eventType.startsWith("providers/firebase.auth/eventTypes/") ||
|
|
282
|
-
eventType.startsWith("google.firebase.testlab.") ||
|
|
283
|
-
eventType.startsWith("google.firebase.remoteconfig.") ||
|
|
284
|
-
eventType.startsWith("google.firebase.firebasealerts.")) {
|
|
285
|
-
return "us-east1";
|
|
286
|
-
}
|
|
287
|
-
if (eventType.startsWith("google.cloud.firestore.")) {
|
|
288
|
-
try {
|
|
289
|
-
const databaseId = eventTrigger.eventFilters?.database || "(default)";
|
|
290
|
-
const db = await (0, firestore_1.getDatabase)(project, databaseId);
|
|
291
|
-
const locationId = db.locationId.toLowerCase();
|
|
292
|
-
if (locationId === "nam5" || locationId === "nam7")
|
|
293
|
-
return "us-central1";
|
|
294
|
-
if (locationId === "eur3")
|
|
295
|
-
return "europe-west1";
|
|
296
|
-
return locationId;
|
|
297
|
-
}
|
|
298
|
-
catch (err) {
|
|
299
|
-
logger_1.logger.debug("Failed to resolve Firestore database location", (0, error_1.getErrStack)(err));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (eventType.startsWith("google.cloud.storage.")) {
|
|
303
|
-
try {
|
|
304
|
-
const bucketName = eventTrigger.eventFilters?.bucket;
|
|
305
|
-
if (bucketName) {
|
|
306
|
-
const bucket = await (0, storage_1.getBucket)(bucketName);
|
|
307
|
-
const locationId = bucket.location.toLowerCase();
|
|
308
|
-
if (locationId === "us")
|
|
309
|
-
return "us-east1";
|
|
310
|
-
if (locationId === "eu")
|
|
311
|
-
return "europe-west1";
|
|
312
|
-
if (locationId === "asia")
|
|
313
|
-
return "asia-east1";
|
|
314
|
-
return locationId;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
catch (err) {
|
|
318
|
-
logger_1.logger.debug("Failed to resolve Cloud Storage bucket location", (0, error_1.getErrStack)(err));
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (eventType.startsWith("google.firebase.database.")) {
|
|
322
|
-
if (eventTrigger.region)
|
|
323
|
-
return eventTrigger.region;
|
|
324
|
-
try {
|
|
325
|
-
const instanceName = eventTrigger.eventFilters?.instance;
|
|
326
|
-
if (instanceName) {
|
|
327
|
-
const details = await (0, database_1.getDatabaseInstanceDetails)(project, instanceName);
|
|
328
|
-
if (details.location && details.location !== "-") {
|
|
329
|
-
return details.location.toLowerCase();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (err) {
|
|
334
|
-
logger_1.logger.debug("Failed to resolve Realtime Database instance location", (0, error_1.getErrStack)(err));
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (eventType.startsWith("google.firebase.dataconnect.")) {
|
|
338
|
-
if (eventTrigger.region)
|
|
339
|
-
return eventTrigger.region;
|
|
340
|
-
try {
|
|
341
|
-
const service = eventTrigger.eventFilters?.service;
|
|
342
|
-
if (service) {
|
|
343
|
-
return (0, names_1.parseServiceName)(service).location;
|
|
344
|
-
}
|
|
345
|
-
const connector = eventTrigger.eventFilters?.connector;
|
|
346
|
-
if (connector) {
|
|
347
|
-
return (0, names_1.parseConnectorName)(connector).location;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch (err) {
|
|
351
|
-
logger_1.logger.debug("Failed to resolve DataConnect location", (0, error_1.getErrStack)(err));
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return exports.DEFAULT_FUNCTION_REGION;
|
|
256
|
+
async function resolveRegionForTrigger(endpoint) {
|
|
257
|
+
const service = (0, services_1.serviceForEndpoint)(endpoint);
|
|
258
|
+
return await service.getDefaultRegion(endpoint);
|
|
355
259
|
}
|
|
356
260
|
function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
357
261
|
for (const wantE of backend.allEndpoints(want)) {
|
|
@@ -4,6 +4,7 @@ exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERA
|
|
|
4
4
|
exports.isAILogicEvent = isAILogicEvent;
|
|
5
5
|
exports.isGlobalAILogicTrigger = isGlobalAILogicTrigger;
|
|
6
6
|
const backend = require("../backend");
|
|
7
|
+
const build = require("../build");
|
|
7
8
|
const error_1 = require("../../../error");
|
|
8
9
|
const ailogicApi = require("../../../gcp/ailogic");
|
|
9
10
|
const ailogic_1 = require("../../../gcp/ailogic");
|
|
@@ -27,19 +28,19 @@ function isGlobalAILogicTrigger(blockingTrigger) {
|
|
|
27
28
|
class AILogicService {
|
|
28
29
|
constructor() {
|
|
29
30
|
this.ensureTriggerRegion = () => Promise.resolve();
|
|
31
|
+
this.requiredProjectBindings = async (projectNumber) => {
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
role: "roles/run.invoker",
|
|
35
|
+
members: [
|
|
36
|
+
`serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
};
|
|
30
41
|
this.name = "ailogic";
|
|
31
42
|
this.api = "firebasevertexai.googleapis.com";
|
|
32
43
|
}
|
|
33
|
-
async requiredProjectBindings(projectNumber) {
|
|
34
|
-
return [
|
|
35
|
-
{
|
|
36
|
-
role: "roles/run.invoker",
|
|
37
|
-
members: [
|
|
38
|
-
`serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
44
|
validateTrigger(endpoint, wantBackend) {
|
|
44
45
|
if (!isAILogicEvent(endpoint)) {
|
|
45
46
|
return;
|
|
@@ -91,5 +92,11 @@ class AILogicService {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
async getDefaultRegion(endpoint) {
|
|
96
|
+
if (build.isBlockingTriggered(endpoint) && isGlobalAILogicTrigger(endpoint.blockingTrigger)) {
|
|
97
|
+
return "us-east1";
|
|
98
|
+
}
|
|
99
|
+
return "us-central1";
|
|
100
|
+
}
|
|
94
101
|
}
|
|
95
102
|
exports.AILogicService = AILogicService;
|
|
@@ -129,5 +129,8 @@ class AuthBlockingService {
|
|
|
129
129
|
this.triggerQueue = this.triggerQueue.then(() => this.unregisterTriggerLocked(ep));
|
|
130
130
|
return this.triggerQueue;
|
|
131
131
|
}
|
|
132
|
+
async getDefaultRegion() {
|
|
133
|
+
return "us-east1";
|
|
134
|
+
}
|
|
132
135
|
}
|
|
133
136
|
exports.AuthBlockingService = AuthBlockingService;
|
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.clearCache = clearCache;
|
|
4
4
|
exports.getDatabaseInstanceDetails = getDatabaseInstanceDetails;
|
|
5
5
|
exports.ensureDatabaseTriggerRegion = ensureDatabaseTriggerRegion;
|
|
6
|
+
exports.getDefaultRegion = getDefaultRegion;
|
|
6
7
|
const error_1 = require("../../../error");
|
|
8
|
+
const build = require("../build");
|
|
7
9
|
const database_1 = require("../../../management/database");
|
|
8
10
|
const instanceCache = new Map();
|
|
9
11
|
function clearCache() {
|
|
@@ -27,3 +29,19 @@ function ensureDatabaseTriggerRegion(endpoint) {
|
|
|
27
29
|
}
|
|
28
30
|
return Promise.resolve();
|
|
29
31
|
}
|
|
32
|
+
async function getDefaultRegion(endpoint) {
|
|
33
|
+
if (!build.isEventTriggered(endpoint)) {
|
|
34
|
+
throw new error_1.FirebaseError("Database getDefaultRegion requires an event-triggered endpoint");
|
|
35
|
+
}
|
|
36
|
+
if (endpoint.eventTrigger.region) {
|
|
37
|
+
return endpoint.eventTrigger.region;
|
|
38
|
+
}
|
|
39
|
+
const instanceName = endpoint.eventTrigger.eventFilters?.instance;
|
|
40
|
+
if (instanceName) {
|
|
41
|
+
const details = await getDatabaseInstanceDetails(endpoint.project, instanceName);
|
|
42
|
+
if (details.location && details.location !== "-") {
|
|
43
|
+
return details.location.toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new error_1.FirebaseError("Could not resolve database instance location");
|
|
47
|
+
}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureDataConnectTriggerRegion = ensureDataConnectTriggerRegion;
|
|
4
4
|
exports.getDataConnectP4SA = getDataConnectP4SA;
|
|
5
|
+
exports.getDefaultRegion = getDefaultRegion;
|
|
5
6
|
const api_1 = require("../../../api");
|
|
6
7
|
const error_1 = require("../../../error");
|
|
8
|
+
const build = require("../build");
|
|
9
|
+
const names_1 = require("../../../dataconnect/names");
|
|
7
10
|
const AUTOPUSH_DATACONNECT_SA_DOMAIN = "gcp-sa-autopush-dataconnect.iam.gserviceaccount.com";
|
|
8
11
|
const STAGING_DATACONNECT_SA_DOMAIN = "gcp-sa-staging-dataconnect.iam.gserviceaccount.com";
|
|
9
12
|
const PROD_DATACONNECT_SA_DOMAIN = "gcp-sa-firebasedataconnect.iam.gserviceaccount.com";
|
|
@@ -26,3 +29,20 @@ function getDataConnectP4SA(projectNumber) {
|
|
|
26
29
|
}
|
|
27
30
|
return `service-${projectNumber}@${PROD_DATACONNECT_SA_DOMAIN}`;
|
|
28
31
|
}
|
|
32
|
+
async function getDefaultRegion(endpoint) {
|
|
33
|
+
if (!build.isEventTriggered(endpoint)) {
|
|
34
|
+
throw new error_1.FirebaseError("DataConnect getDefaultRegion requires an event-triggered endpoint");
|
|
35
|
+
}
|
|
36
|
+
if (endpoint.eventTrigger.region) {
|
|
37
|
+
return endpoint.eventTrigger.region;
|
|
38
|
+
}
|
|
39
|
+
const service = endpoint.eventTrigger.eventFilters?.service;
|
|
40
|
+
if (service) {
|
|
41
|
+
return (0, names_1.parseServiceName)(service).location;
|
|
42
|
+
}
|
|
43
|
+
const connector = endpoint.eventTrigger.eventFilters?.connector;
|
|
44
|
+
if (connector) {
|
|
45
|
+
return (0, names_1.parseConnectorName)(connector).location;
|
|
46
|
+
}
|
|
47
|
+
throw new error_1.FirebaseError("Could not resolve DataConnect location");
|
|
48
|
+
}
|
|
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.clearCache = clearCache;
|
|
4
4
|
exports.getDatabase = getDatabase;
|
|
5
5
|
exports.ensureFirestoreTriggerRegion = ensureFirestoreTriggerRegion;
|
|
6
|
+
exports.getDefaultRegion = getDefaultRegion;
|
|
6
7
|
const firestore = require("../../../gcp/firestore");
|
|
7
8
|
const error_1 = require("../../../error");
|
|
9
|
+
const build = require("../build");
|
|
10
|
+
const location_1 = require("../../../gcp/location");
|
|
8
11
|
const dbCache = new Map();
|
|
9
12
|
const dbPromiseCache = new Map();
|
|
10
13
|
function clearCache() {
|
|
@@ -43,3 +46,12 @@ async function ensureFirestoreTriggerRegion(endpoint) {
|
|
|
43
46
|
throw new error_1.FirebaseError("A firestore trigger location must match the firestore database region.");
|
|
44
47
|
}
|
|
45
48
|
}
|
|
49
|
+
async function getDefaultRegion(endpoint) {
|
|
50
|
+
if (!build.isEventTriggered(endpoint)) {
|
|
51
|
+
throw new error_1.FirebaseError("Firestore getDefaultRegion requires an event-triggered endpoint");
|
|
52
|
+
}
|
|
53
|
+
const databaseId = endpoint.eventTrigger.eventFilters?.database || "(default)";
|
|
54
|
+
const db = await getDatabase(endpoint.project, databaseId);
|
|
55
|
+
const locationId = db.locationId.toLowerCase();
|
|
56
|
+
return location_1.FIRESTORE_DUAL_REGION_TO_REGION_MAPPING[locationId] || locationId;
|
|
57
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noopProjectBindings = exports.noop = void 0;
|
|
3
|
+
exports.FALLBACK_DEPLOYMENT_REGION = exports.DEFAULT_GLOBAL_TRIGGER_REGION = exports.noopProjectBindings = exports.noop = void 0;
|
|
4
4
|
exports.serviceForEndpoint = serviceForEndpoint;
|
|
5
|
-
const backend = require("../backend");
|
|
6
5
|
const auth_1 = require("./auth");
|
|
7
6
|
const storage_1 = require("./storage");
|
|
8
7
|
const firebaseAlerts_1 = require("./firebaseAlerts");
|
|
@@ -16,6 +15,8 @@ const noop = () => Promise.resolve();
|
|
|
16
15
|
exports.noop = noop;
|
|
17
16
|
const noopProjectBindings = () => Promise.resolve([]);
|
|
18
17
|
exports.noopProjectBindings = noopProjectBindings;
|
|
18
|
+
exports.DEFAULT_GLOBAL_TRIGGER_REGION = "us-east1";
|
|
19
|
+
exports.FALLBACK_DEPLOYMENT_REGION = "us-central1";
|
|
19
20
|
const noOpService = {
|
|
20
21
|
name: "noop",
|
|
21
22
|
api: "",
|
|
@@ -23,6 +24,7 @@ const noOpService = {
|
|
|
23
24
|
validateTrigger: exports.noop,
|
|
24
25
|
registerTrigger: exports.noop,
|
|
25
26
|
unregisterTrigger: exports.noop,
|
|
27
|
+
getDefaultRegion: () => Promise.resolve(exports.FALLBACK_DEPLOYMENT_REGION),
|
|
26
28
|
};
|
|
27
29
|
const pubSubService = {
|
|
28
30
|
name: "pubsub",
|
|
@@ -32,6 +34,7 @@ const pubSubService = {
|
|
|
32
34
|
validateTrigger: exports.noop,
|
|
33
35
|
registerTrigger: exports.noop,
|
|
34
36
|
unregisterTrigger: exports.noop,
|
|
37
|
+
getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
|
|
35
38
|
};
|
|
36
39
|
const storageService = {
|
|
37
40
|
name: "storage",
|
|
@@ -41,6 +44,7 @@ const storageService = {
|
|
|
41
44
|
validateTrigger: exports.noop,
|
|
42
45
|
registerTrigger: exports.noop,
|
|
43
46
|
unregisterTrigger: exports.noop,
|
|
47
|
+
getDefaultRegion: storage_1.getDefaultRegion,
|
|
44
48
|
};
|
|
45
49
|
const firebaseAlertsService = {
|
|
46
50
|
name: "firebasealerts",
|
|
@@ -50,6 +54,7 @@ const firebaseAlertsService = {
|
|
|
50
54
|
validateTrigger: exports.noop,
|
|
51
55
|
registerTrigger: exports.noop,
|
|
52
56
|
unregisterTrigger: exports.noop,
|
|
57
|
+
getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
|
|
53
58
|
};
|
|
54
59
|
const authBlockingService = new auth_1.AuthBlockingService();
|
|
55
60
|
const aiLogicService = new ailogic_1.AILogicService();
|
|
@@ -61,6 +66,7 @@ const databaseService = {
|
|
|
61
66
|
validateTrigger: exports.noop,
|
|
62
67
|
registerTrigger: exports.noop,
|
|
63
68
|
unregisterTrigger: exports.noop,
|
|
69
|
+
getDefaultRegion: database_1.getDefaultRegion,
|
|
64
70
|
};
|
|
65
71
|
const remoteConfigService = {
|
|
66
72
|
name: "remoteconfig",
|
|
@@ -70,6 +76,7 @@ const remoteConfigService = {
|
|
|
70
76
|
validateTrigger: exports.noop,
|
|
71
77
|
registerTrigger: exports.noop,
|
|
72
78
|
unregisterTrigger: exports.noop,
|
|
79
|
+
getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
|
|
73
80
|
};
|
|
74
81
|
const testLabService = {
|
|
75
82
|
name: "testlab",
|
|
@@ -79,6 +86,7 @@ const testLabService = {
|
|
|
79
86
|
validateTrigger: exports.noop,
|
|
80
87
|
registerTrigger: exports.noop,
|
|
81
88
|
unregisterTrigger: exports.noop,
|
|
89
|
+
getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
|
|
82
90
|
};
|
|
83
91
|
const firestoreService = {
|
|
84
92
|
name: "firestore",
|
|
@@ -88,6 +96,7 @@ const firestoreService = {
|
|
|
88
96
|
validateTrigger: exports.noop,
|
|
89
97
|
registerTrigger: exports.noop,
|
|
90
98
|
unregisterTrigger: exports.noop,
|
|
99
|
+
getDefaultRegion: firestore_1.getDefaultRegion,
|
|
91
100
|
};
|
|
92
101
|
const dataconnectService = {
|
|
93
102
|
name: "dataconnect",
|
|
@@ -97,6 +106,7 @@ const dataconnectService = {
|
|
|
97
106
|
validateTrigger: exports.noop,
|
|
98
107
|
registerTrigger: exports.noop,
|
|
99
108
|
unregisterTrigger: exports.noop,
|
|
109
|
+
getDefaultRegion: dataconnect_1.getDefaultRegion,
|
|
100
110
|
};
|
|
101
111
|
const EVENT_SERVICE_MAPPING = {
|
|
102
112
|
"google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
|
|
@@ -128,11 +138,12 @@ const EVENT_SERVICE_MAPPING = {
|
|
|
128
138
|
"google.firebase.ailogic.v1.afterGenerate": aiLogicService,
|
|
129
139
|
};
|
|
130
140
|
function serviceForEndpoint(endpoint) {
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
let eventType;
|
|
142
|
+
if ("eventTrigger" in endpoint && endpoint.eventTrigger?.eventType) {
|
|
143
|
+
eventType = endpoint.eventTrigger.eventType;
|
|
133
144
|
}
|
|
134
|
-
if (
|
|
135
|
-
|
|
145
|
+
else if ("blockingTrigger" in endpoint && endpoint.blockingTrigger?.eventType) {
|
|
146
|
+
eventType = endpoint.blockingTrigger.eventType;
|
|
136
147
|
}
|
|
137
|
-
return noOpService;
|
|
148
|
+
return eventType ? EVENT_SERVICE_MAPPING[eventType] || noOpService : noOpService;
|
|
138
149
|
}
|
|
@@ -4,10 +4,12 @@ exports.clearCache = clearCache;
|
|
|
4
4
|
exports.getBucket = getBucket;
|
|
5
5
|
exports.obtainStorageBindings = obtainStorageBindings;
|
|
6
6
|
exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
|
|
7
|
+
exports.getDefaultRegion = getDefaultRegion;
|
|
7
8
|
const storage = require("../../../gcp/storage");
|
|
8
9
|
const logger_1 = require("../../../logger");
|
|
9
10
|
const error_1 = require("../../../error");
|
|
10
11
|
const location_1 = require("../../../gcp/location");
|
|
12
|
+
const build = require("../build");
|
|
11
13
|
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
12
14
|
const bucketCache = new Map();
|
|
13
15
|
function clearCache() {
|
|
@@ -54,3 +56,15 @@ async function ensureStorageTriggerRegion(endpoint) {
|
|
|
54
56
|
throw new error_1.FirebaseError(`A function in region ${endpoint.region} cannot listen to a bucket in region ${eventTrigger.region}`);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
async function getDefaultRegion(endpoint) {
|
|
60
|
+
if (!build.isEventTriggered(endpoint)) {
|
|
61
|
+
throw new error_1.FirebaseError("Storage getDefaultRegion requires an event-triggered endpoint");
|
|
62
|
+
}
|
|
63
|
+
const bucketName = endpoint.eventTrigger.eventFilters?.bucket;
|
|
64
|
+
if (!bucketName) {
|
|
65
|
+
throw new error_1.FirebaseError("Could not find bucket name in event trigger filters");
|
|
66
|
+
}
|
|
67
|
+
const bucket = await getBucket(bucketName);
|
|
68
|
+
const locationId = bucket.location.toLowerCase();
|
|
69
|
+
return location_1.STORAGE_MULTI_REGION_TO_REGION_MAPPING[locationId] || locationId;
|
|
70
|
+
}
|