firebase-tools 9.20.0 → 9.21.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/CHANGELOG.md +3 -1
- package/lib/apiv2.js +4 -2
- package/lib/commands/crashlytics-symbols-upload.js +1 -1
- package/lib/commands/ext-dev-unpublish.js +10 -3
- package/lib/commands/functions-delete.js +53 -42
- package/lib/commands/functions-list.js +11 -11
- package/lib/deploy/functions/backend.js +77 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +61 -29
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +362 -0
- package/lib/deploy/functions/release/index.js +69 -0
- package/lib/deploy/functions/release/planner.js +159 -0
- package/lib/deploy/functions/release/reporter.js +162 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -127
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +22 -43
- package/lib/deploy/functions/triggerRegionHelper.js +28 -20
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/emulator/auth/apiSpec.js +37 -6
- package/lib/emulator/auth/operations.js +18 -8
- package/lib/emulator/auth/server.js +16 -2
- package/lib/emulator/auth/state.js +34 -15
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +15 -3
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/extensionsHelper.js +1 -1
- package/lib/gcp/cloudfunctions.js +1 -68
- package/lib/gcp/cloudfunctionsv2.js +2 -94
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/pubsub.js +1 -9
- package/lib/utils.js +30 -1
- package/package.json +1 -1
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
-
|
|
1
|
+
- Fix Auth Emulator deleteTenant not working with Node Admin (#3817).
|
|
2
|
+
- Fix Crashlytics Android Native Symbols not working on Windows due to ":" in the path (#3842)
|
|
3
|
+
- Fixes Firestore emulator UI showing requests out of order
|
package/lib/apiv2.js
CHANGED
|
@@ -118,8 +118,10 @@ class Client {
|
|
|
118
118
|
reqOptions.headers.set("User-Agent", `FirebaseCLI/${CLI_VERSION}`);
|
|
119
119
|
}
|
|
120
120
|
reqOptions.headers.set("X-Client-Version", `FirebaseCLI/${CLI_VERSION}`);
|
|
121
|
-
if (reqOptions.
|
|
122
|
-
reqOptions.
|
|
121
|
+
if (!reqOptions.headers.has("Content-Type")) {
|
|
122
|
+
if (reqOptions.responseType === "json") {
|
|
123
|
+
reqOptions.headers.set("Content-Type", "application/json");
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
return reqOptions;
|
|
125
127
|
}
|
|
@@ -40,7 +40,7 @@ exports.default = new command_1.Command("crashlytics:symbols:upload <symbolFiles
|
|
|
40
40
|
jarFile,
|
|
41
41
|
app,
|
|
42
42
|
generator,
|
|
43
|
-
cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${uuid.v4()}`, "nativeSymbols", app, generator),
|
|
43
|
+
cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${uuid.v4()}`, "nativeSymbols", app.replace(/:/g, "-"), generator),
|
|
44
44
|
symbolFile: "",
|
|
45
45
|
generate: true,
|
|
46
46
|
};
|
|
@@ -12,11 +12,12 @@ const error_1 = require("../error");
|
|
|
12
12
|
const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
|
|
13
13
|
module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
|
|
14
14
|
.description("unpublish an extension")
|
|
15
|
+
.withForce()
|
|
15
16
|
.help("use this command to unpublish an extension, and make it unavailable for developers to install or reconfigure. " +
|
|
16
17
|
"Specify the extension you want to unpublish using the format '<publisherId>/<extensionId>.")
|
|
17
18
|
.before(requireAuth_1.requireAuth)
|
|
18
19
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
|
|
19
|
-
.action(async (extensionRef) => {
|
|
20
|
+
.action(async (extensionRef, options) => {
|
|
20
21
|
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
21
22
|
utils.logLabeledWarning(extensionsHelper_1.logPrefix, "If you unpublish this extension, developers won't be able to install it. For developers who currently have this extension installed, it will continue to run and will appear as unpublished when listed in the Firebase console or Firebase CLI.");
|
|
22
23
|
utils.logLabeledWarning("This is a permanent action", `Once unpublished, you may never use the extension name '${clc.bold(extensionId)}' again.`);
|
|
@@ -24,14 +25,20 @@ module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
|
|
|
24
25
|
throw new error_1.FirebaseError(`Unpublishing a single version is not currently supported. You can only unpublish ${clc.bold("ALL versions")} of an extension. To unpublish all versions, please remove the version from the reference.`);
|
|
25
26
|
}
|
|
26
27
|
await extensionsApi_1.getExtension(extensionRef);
|
|
27
|
-
const consent = await comfirmUnpublish(publisherId, extensionId);
|
|
28
|
+
const consent = await comfirmUnpublish(publisherId, extensionId, options);
|
|
28
29
|
if (!consent) {
|
|
29
30
|
throw new error_1.FirebaseError("unpublishing cancelled.");
|
|
30
31
|
}
|
|
31
32
|
await extensionsApi_1.unpublishExtension(extensionRef);
|
|
32
33
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully unpublished all versions of this extension.");
|
|
33
34
|
});
|
|
34
|
-
async function comfirmUnpublish(publisherId, extensionId) {
|
|
35
|
+
async function comfirmUnpublish(publisherId, extensionId, options) {
|
|
36
|
+
if (options.nonInteractive && !options.force) {
|
|
37
|
+
throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
|
|
38
|
+
}
|
|
39
|
+
if (options.nonInteractive && options.force) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
35
42
|
const message = `You are about to unpublish ALL versions of ${clc.green(`${publisherId}/${extensionId}`)}.\nDo you wish to continue? `;
|
|
36
43
|
return prompt_1.promptOnce({
|
|
37
44
|
type: "confirm",
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const command_1 = require("../command");
|
|
4
3
|
const clc = require("cli-color");
|
|
5
4
|
const functionsConfig = require("../functionsConfig");
|
|
6
|
-
const
|
|
5
|
+
const command_1 = require("../command");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
7
|
const projectUtils_1 = require("../projectUtils");
|
|
8
8
|
const prompt_1 = require("../prompt");
|
|
9
|
-
const
|
|
9
|
+
const functional_1 = require("../functional");
|
|
10
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
11
|
+
const helper = require("../deploy/functions/functionsDeployHelper");
|
|
11
12
|
const utils = require("../utils");
|
|
12
13
|
const backend = require("../deploy/functions/backend");
|
|
13
|
-
const
|
|
14
|
+
const planner = require("../deploy/functions/release/planner");
|
|
15
|
+
const fabricator = require("../deploy/functions/release/fabricator");
|
|
16
|
+
const executor = require("../deploy/functions/release/executor");
|
|
17
|
+
const reporter = require("../deploy/functions/release/reporter");
|
|
14
18
|
exports.default = new command_1.Command("functions:delete [filters...]")
|
|
15
19
|
.description("delete one or more Cloud Functions by name or group name.")
|
|
16
20
|
.option("--region <region>", "Specify region of the function to be deleted. " +
|
|
@@ -23,48 +27,55 @@ exports.default = new command_1.Command("functions:delete [filters...]")
|
|
|
23
27
|
}
|
|
24
28
|
const context = {
|
|
25
29
|
projectId: projectUtils_1.needProjectId(options),
|
|
30
|
+
filters: filters.map((f) => f.split(".")),
|
|
26
31
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
32
|
+
const [config, existingBackend] = await Promise.all([
|
|
33
|
+
functionsConfig.getFirebaseConfig(options),
|
|
34
|
+
backend.existingBackend(context),
|
|
35
|
+
]);
|
|
36
|
+
await backend.checkAvailability(context, backend.empty());
|
|
37
|
+
const appEngineLocation = functionsConfig.getAppEngineLocation(config);
|
|
38
|
+
if (options.region) {
|
|
39
|
+
existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] };
|
|
40
|
+
}
|
|
41
|
+
const plan = planner.createDeploymentPlan(backend.empty(), existingBackend, {
|
|
42
|
+
filters: context.filters,
|
|
43
|
+
deleteAll: true,
|
|
44
|
+
});
|
|
45
|
+
const allEpToDelete = Object.values(plan)
|
|
46
|
+
.map((changes) => changes.endpointsToDelete)
|
|
47
|
+
.reduce(functional_1.reduceFlat, [])
|
|
48
|
+
.sort(backend.compareFunctions);
|
|
49
|
+
if (allEpToDelete.length === 0) {
|
|
50
|
+
throw new error_1.FirebaseError(`The specified filters do not match any existing functions in project ${clc.bold(context.projectId)}.`);
|
|
51
|
+
}
|
|
52
|
+
const deleteList = allEpToDelete.map((func) => `\t${helper.getFunctionLabel(func)}`).join("\n");
|
|
53
|
+
const confirmDeletion = await prompt_1.promptOnce({
|
|
54
|
+
type: "confirm",
|
|
55
|
+
name: "force",
|
|
56
|
+
default: false,
|
|
57
|
+
message: "You are about to delete the following Cloud Functions:\n" +
|
|
58
|
+
deleteList +
|
|
59
|
+
"\n Are you sure?",
|
|
60
|
+
}, options);
|
|
61
|
+
if (!confirmDeletion) {
|
|
62
|
+
throw new error_1.FirebaseError("Command aborted.");
|
|
63
|
+
}
|
|
64
|
+
const functionExecutor = new executor.QueueExecutor({
|
|
65
|
+
retries: 30,
|
|
66
|
+
backoff: 20000,
|
|
67
|
+
concurrency: 40,
|
|
68
|
+
maxBackoff: 40000,
|
|
29
69
|
});
|
|
30
70
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await backend.checkAvailability(context, backend.empty());
|
|
36
|
-
const appEngineLocation = functionsConfig.getAppEngineLocation(config);
|
|
37
|
-
const functionsToDelete = existingBackend.cloudFunctions.filter((fn) => {
|
|
38
|
-
const regionMatches = options.region ? fn.region === options.region : true;
|
|
39
|
-
const nameMatches = helper.functionMatchesAnyGroup(fn, filterChunks);
|
|
40
|
-
return regionMatches && nameMatches;
|
|
41
|
-
});
|
|
42
|
-
if (functionsToDelete.length === 0) {
|
|
43
|
-
throw new Error(`The specified filters do not match any existing functions in project ${clc.bold(context.projectId)}.`);
|
|
44
|
-
}
|
|
45
|
-
const schedulesToDelete = existingBackend.schedules.filter((schedule) => {
|
|
46
|
-
functionsToDelete.some(backend.sameFunctionName(schedule.targetService));
|
|
47
|
-
});
|
|
48
|
-
const topicsToDelete = existingBackend.topics.filter((topic) => {
|
|
49
|
-
functionsToDelete.some(backend.sameFunctionName(topic.targetService));
|
|
71
|
+
const fab = new fabricator.Fabricator({
|
|
72
|
+
functionExecutor,
|
|
73
|
+
executor: new executor.QueueExecutor({}),
|
|
74
|
+
appEngineLocation,
|
|
50
75
|
});
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
.join("\n");
|
|
56
|
-
const confirmDeletion = await prompt_1.promptOnce({
|
|
57
|
-
type: "confirm",
|
|
58
|
-
name: "force",
|
|
59
|
-
default: false,
|
|
60
|
-
message: "You are about to delete the following Cloud Functions:\n" +
|
|
61
|
-
deleteList +
|
|
62
|
-
"\n Are you sure?",
|
|
63
|
-
}, options);
|
|
64
|
-
if (!confirmDeletion) {
|
|
65
|
-
throw new Error("Command aborted.");
|
|
66
|
-
}
|
|
67
|
-
return await functionsDelete_1.deleteFunctions(functionsToDelete, schedulesToDelete, topicsToDelete, appEngineLocation);
|
|
76
|
+
const summary = await fab.applyPlan(plan);
|
|
77
|
+
await reporter.logAndTrackDeployStats(summary);
|
|
78
|
+
reporter.printErrors(summary);
|
|
68
79
|
}
|
|
69
80
|
catch (err) {
|
|
70
81
|
throw new error_1.FirebaseError("Failed to delete functions", {
|
|
@@ -5,7 +5,6 @@ const error_1 = require("../error");
|
|
|
5
5
|
const projectUtils_1 = require("../projectUtils");
|
|
6
6
|
const requirePermissions_1 = require("../requirePermissions");
|
|
7
7
|
const backend = require("../deploy/functions/backend");
|
|
8
|
-
const listFunctions_1 = require("../functions/listFunctions");
|
|
9
8
|
const previews_1 = require("../previews");
|
|
10
9
|
const logger_1 = require("../logger");
|
|
11
10
|
const Table = require("cli-table");
|
|
@@ -17,7 +16,8 @@ exports.default = new command_1.Command("functions:list")
|
|
|
17
16
|
const context = {
|
|
18
17
|
projectId: projectUtils_1.needProjectId(options),
|
|
19
18
|
};
|
|
20
|
-
const
|
|
19
|
+
const existing = await backend.existingBackend(context);
|
|
20
|
+
const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions);
|
|
21
21
|
const table = previews_1.previews.functionsv2
|
|
22
22
|
? new Table({
|
|
23
23
|
head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
|
|
@@ -27,23 +27,23 @@ exports.default = new command_1.Command("functions:list")
|
|
|
27
27
|
head: ["Function", "Trigger", "Location", "Memory", "Runtime"],
|
|
28
28
|
style: { head: ["yellow"] },
|
|
29
29
|
});
|
|
30
|
-
for (const
|
|
31
|
-
const trigger = backend.
|
|
32
|
-
const availableMemoryMb =
|
|
30
|
+
for (const endpoint of endpointsList) {
|
|
31
|
+
const trigger = backend.endpointTriggerType(endpoint);
|
|
32
|
+
const availableMemoryMb = endpoint.availableMemoryMb || "---";
|
|
33
33
|
const entry = previews_1.previews.functionsv2
|
|
34
34
|
? [
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
endpoint.id,
|
|
36
|
+
endpoint.platform === "gcfv2" ? "v2" : "v1",
|
|
37
37
|
trigger,
|
|
38
|
-
|
|
38
|
+
endpoint.region,
|
|
39
39
|
availableMemoryMb,
|
|
40
|
-
|
|
40
|
+
endpoint.runtime,
|
|
41
41
|
]
|
|
42
|
-
: [
|
|
42
|
+
: [endpoint.id, trigger, endpoint.region, availableMemoryMb, endpoint.runtime];
|
|
43
43
|
table.push(entry);
|
|
44
44
|
}
|
|
45
45
|
logger_1.logger.info(table.toString());
|
|
46
|
-
return
|
|
46
|
+
return endpointsList;
|
|
47
47
|
}
|
|
48
48
|
catch (err) {
|
|
49
49
|
throw new error_1.FirebaseError("Failed to list functions", {
|
|
@@ -1,38 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = exports.
|
|
3
|
+
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
|
|
4
4
|
const gcf = require("../../gcp/cloudfunctions");
|
|
5
5
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
6
6
|
const utils = require("../../utils");
|
|
7
7
|
const error_1 = require("../../error");
|
|
8
8
|
const previews_1 = require("../../previews");
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exports.isEventTrigger = isEventTrigger;
|
|
13
|
-
function triggerTag(fn) {
|
|
14
|
-
var _a, _b;
|
|
15
|
-
if ((_a = fn.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
|
|
16
|
-
if (fn.platform === "gcfv1") {
|
|
17
|
-
return "v1.scheduled";
|
|
18
|
-
}
|
|
19
|
-
return "v2.scheduled";
|
|
9
|
+
function endpointTriggerType(endpoint) {
|
|
10
|
+
if (isScheduleTriggered(endpoint)) {
|
|
11
|
+
return "scheduled";
|
|
20
12
|
}
|
|
21
|
-
if ((
|
|
22
|
-
|
|
23
|
-
return "v1.callable";
|
|
24
|
-
}
|
|
25
|
-
return "v2.callable";
|
|
13
|
+
else if (isHttpsTriggered(endpoint)) {
|
|
14
|
+
return "https";
|
|
26
15
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
else if (isEventTriggered(endpoint)) {
|
|
17
|
+
return endpoint.eventTrigger.eventType;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
|
|
32
21
|
}
|
|
33
|
-
return fn.trigger.eventType;
|
|
34
22
|
}
|
|
35
|
-
exports.
|
|
23
|
+
exports.endpointTriggerType = endpointTriggerType;
|
|
36
24
|
function memoryOptionDisplayName(option) {
|
|
37
25
|
return {
|
|
38
26
|
128: "128MB",
|
|
@@ -61,36 +49,31 @@ exports.isScheduleTriggered = isScheduleTriggered;
|
|
|
61
49
|
function empty() {
|
|
62
50
|
return {
|
|
63
51
|
requiredAPIs: {},
|
|
64
|
-
endpoints:
|
|
65
|
-
cloudFunctions: [],
|
|
66
|
-
schedules: [],
|
|
67
|
-
topics: [],
|
|
52
|
+
endpoints: {},
|
|
68
53
|
environmentVariables: {},
|
|
69
54
|
};
|
|
70
55
|
}
|
|
71
56
|
exports.empty = empty;
|
|
57
|
+
function of(...endpoints) {
|
|
58
|
+
const bkend = Object.assign({}, empty());
|
|
59
|
+
for (const endpoint of endpoints) {
|
|
60
|
+
bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
|
|
61
|
+
if (bkend.endpoints[endpoint.region][endpoint.id]) {
|
|
62
|
+
throw new Error("Trying to create a backend with the same endpiont twice");
|
|
63
|
+
}
|
|
64
|
+
bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
65
|
+
}
|
|
66
|
+
return bkend;
|
|
67
|
+
}
|
|
68
|
+
exports.of = of;
|
|
72
69
|
function isEmptyBackend(backend) {
|
|
73
|
-
return (Object.keys(backend.requiredAPIs).length == 0 &&
|
|
74
|
-
backend.cloudFunctions.length === 0 &&
|
|
75
|
-
backend.schedules.length === 0 &&
|
|
76
|
-
backend.topics.length === 0);
|
|
70
|
+
return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
|
|
77
71
|
}
|
|
78
72
|
exports.isEmptyBackend = isEmptyBackend;
|
|
79
73
|
function functionName(cloudFunction) {
|
|
80
74
|
return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
|
|
81
75
|
}
|
|
82
76
|
exports.functionName = functionName;
|
|
83
|
-
exports.sameFunctionName = (func) => (test) => {
|
|
84
|
-
return func.id === test.id && func.region === test.region && func.project == test.project;
|
|
85
|
-
};
|
|
86
|
-
function scheduleName(schedule, appEngineLocation) {
|
|
87
|
-
return `projects/${schedule.project}/locations/${appEngineLocation}/jobs/${schedule.id}`;
|
|
88
|
-
}
|
|
89
|
-
exports.scheduleName = scheduleName;
|
|
90
|
-
function topicName(topic) {
|
|
91
|
-
return `projects/${topic.project}/topics/${topic.id}`;
|
|
92
|
-
}
|
|
93
|
-
exports.topicName = topicName;
|
|
94
77
|
function scheduleIdForFunction(cloudFunction) {
|
|
95
78
|
return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
|
|
96
79
|
}
|
|
@@ -104,7 +87,7 @@ async function existingBackend(context, forceRefresh) {
|
|
|
104
87
|
}
|
|
105
88
|
exports.existingBackend = existingBackend;
|
|
106
89
|
async function loadExistingBackend(ctx) {
|
|
107
|
-
var _a
|
|
90
|
+
var _a;
|
|
108
91
|
ctx.loadedExistingBackend = true;
|
|
109
92
|
ctx.existingBackend = Object.assign({}, empty());
|
|
110
93
|
ctx.unreachableRegions = {
|
|
@@ -113,32 +96,10 @@ async function loadExistingBackend(ctx) {
|
|
|
113
96
|
};
|
|
114
97
|
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
|
|
115
98
|
for (const apiFunction of gcfV1Results.functions) {
|
|
116
|
-
const
|
|
117
|
-
ctx.existingBackend.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const id = scheduleIdForFunction(specFunction);
|
|
121
|
-
ctx.existingBackend.schedules.push({
|
|
122
|
-
id,
|
|
123
|
-
project: specFunction.project,
|
|
124
|
-
transport: "pubsub",
|
|
125
|
-
targetService: {
|
|
126
|
-
id: specFunction.id,
|
|
127
|
-
region: specFunction.region,
|
|
128
|
-
project: specFunction.project,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
ctx.existingBackend.topics.push({
|
|
132
|
-
id,
|
|
133
|
-
project: specFunction.project,
|
|
134
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
135
|
-
targetService: {
|
|
136
|
-
id: specFunction.id,
|
|
137
|
-
region: specFunction.region,
|
|
138
|
-
project: specFunction.project,
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
}
|
|
99
|
+
const endpoint = gcf.endpointFromFunction(apiFunction);
|
|
100
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
101
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
102
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
142
103
|
}
|
|
143
104
|
ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
|
|
144
105
|
if (!previews_1.previews.functionsv2) {
|
|
@@ -149,52 +110,16 @@ async function loadExistingBackend(ctx) {
|
|
|
149
110
|
gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
|
|
150
111
|
}
|
|
151
112
|
catch (err) {
|
|
152
|
-
if (err.status === 404 && ((
|
|
113
|
+
if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
|
|
153
114
|
return;
|
|
154
115
|
}
|
|
155
116
|
throw err;
|
|
156
117
|
}
|
|
157
118
|
for (const apiFunction of gcfV2Results.functions) {
|
|
158
|
-
const
|
|
159
|
-
ctx.existingBackend.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (pubsubScheduled) {
|
|
163
|
-
const id = scheduleIdForFunction(specFunction);
|
|
164
|
-
ctx.existingBackend.schedules.push({
|
|
165
|
-
id,
|
|
166
|
-
project: specFunction.project,
|
|
167
|
-
transport: "pubsub",
|
|
168
|
-
targetService: {
|
|
169
|
-
id: specFunction.id,
|
|
170
|
-
region: specFunction.region,
|
|
171
|
-
project: specFunction.project,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
ctx.existingBackend.topics.push({
|
|
175
|
-
id,
|
|
176
|
-
project: specFunction.project,
|
|
177
|
-
labels: exports.SCHEDULED_FUNCTION_LABEL,
|
|
178
|
-
targetService: {
|
|
179
|
-
id: specFunction.id,
|
|
180
|
-
region: specFunction.region,
|
|
181
|
-
project: specFunction.project,
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
if (httpsScheduled) {
|
|
186
|
-
const id = scheduleIdForFunction(specFunction);
|
|
187
|
-
ctx.existingBackend.schedules.push({
|
|
188
|
-
id,
|
|
189
|
-
project: specFunction.project,
|
|
190
|
-
transport: "https",
|
|
191
|
-
targetService: {
|
|
192
|
-
id: specFunction.id,
|
|
193
|
-
region: specFunction.region,
|
|
194
|
-
project: specFunction.project,
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
}
|
|
119
|
+
const endpoint = gcfV2.endpointFromFunction(apiFunction);
|
|
120
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
121
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
122
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
198
123
|
}
|
|
199
124
|
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
|
|
200
125
|
}
|
|
@@ -205,12 +130,12 @@ async function checkAvailability(context, want) {
|
|
|
205
130
|
}
|
|
206
131
|
const gcfV1Regions = new Set();
|
|
207
132
|
const gcfV2Regions = new Set();
|
|
208
|
-
for (const
|
|
209
|
-
if (
|
|
210
|
-
gcfV1Regions.add(
|
|
133
|
+
for (const ep of allEndpoints(want)) {
|
|
134
|
+
if (ep.platform == "gcfv1") {
|
|
135
|
+
gcfV1Regions.add(ep.region);
|
|
211
136
|
}
|
|
212
137
|
else {
|
|
213
|
-
gcfV2Regions.add(
|
|
138
|
+
gcfV2Regions.add(ep.region);
|
|
214
139
|
}
|
|
215
140
|
}
|
|
216
141
|
const neededUnreachableV1 = ctx.unreachableRegions.gcfV1.filter((region) => gcfV1Regions.has(region));
|
|
@@ -237,6 +162,43 @@ async function checkAvailability(context, want) {
|
|
|
237
162
|
}
|
|
238
163
|
}
|
|
239
164
|
exports.checkAvailability = checkAvailability;
|
|
165
|
+
function allEndpoints(backend) {
|
|
166
|
+
return Object.values(backend.endpoints).reduce((accum, perRegion) => {
|
|
167
|
+
return [...accum, ...Object.values(perRegion)];
|
|
168
|
+
}, []);
|
|
169
|
+
}
|
|
170
|
+
exports.allEndpoints = allEndpoints;
|
|
171
|
+
function someEndpoint(backend, predicate) {
|
|
172
|
+
for (const endpoints of Object.values(backend.endpoints)) {
|
|
173
|
+
if (Object.values(endpoints).some(predicate)) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
exports.someEndpoint = someEndpoint;
|
|
180
|
+
function matchingBackend(backend, predicate) {
|
|
181
|
+
const filtered = Object.assign({}, empty());
|
|
182
|
+
for (const endpoint of allEndpoints(backend)) {
|
|
183
|
+
if (!predicate(endpoint)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
|
|
187
|
+
filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
188
|
+
}
|
|
189
|
+
return filtered;
|
|
190
|
+
}
|
|
191
|
+
exports.matchingBackend = matchingBackend;
|
|
192
|
+
function regionalEndpoints(backend, region) {
|
|
193
|
+
return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
|
|
194
|
+
}
|
|
195
|
+
exports.regionalEndpoints = regionalEndpoints;
|
|
196
|
+
exports.hasEndpoint = (backend) => (endpoint) => {
|
|
197
|
+
return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id];
|
|
198
|
+
};
|
|
199
|
+
exports.missingEndpoint = (backend) => (endpoint) => {
|
|
200
|
+
return !exports.hasEndpoint(backend)(endpoint);
|
|
201
|
+
};
|
|
240
202
|
function compareFunctions(left, right) {
|
|
241
203
|
if (left.platform != right.platform) {
|
|
242
204
|
return right.platform < left.platform ? -1 : 1;
|
|
@@ -28,17 +28,17 @@ async function checkServiceAccountIam(projectId) {
|
|
|
28
28
|
}
|
|
29
29
|
exports.checkServiceAccountIam = checkServiceAccountIam;
|
|
30
30
|
async function checkHttpIam(context, options, payload) {
|
|
31
|
-
const functions = payload.functions.backend.cloudFunctions;
|
|
32
31
|
const filterGroups = context.filters || functionsDeployHelper_1.getFilterGroups(options);
|
|
33
|
-
const
|
|
34
|
-
.
|
|
32
|
+
const httpEndpoints = backend
|
|
33
|
+
.allEndpoints(payload.functions.backend)
|
|
34
|
+
.filter(backend.isHttpsTriggered)
|
|
35
35
|
.filter((f) => functionsDeployHelper_1.functionMatchesAnyGroup(f, filterGroups));
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
36
|
+
const existing = await backend.existingBackend(context);
|
|
37
|
+
const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
|
|
38
|
+
if (newHttpsEndpoints.length === 0) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
logger_1.logger.debug("[functions] found",
|
|
41
|
+
logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
|
|
42
42
|
let passed = true;
|
|
43
43
|
try {
|
|
44
44
|
const iamResult = await iam_1.testIamPermissions(context.projectId, [PERMISSION]);
|
|
@@ -51,7 +51,7 @@ async function checkHttpIam(context, options, payload) {
|
|
|
51
51
|
if (!passed) {
|
|
52
52
|
track("Error (User)", "deploy:functions:http_create_missing_iam");
|
|
53
53
|
throw new error_1.FirebaseError(`Missing required permission on project ${cli_color_1.bold(context.projectId)} to deploy new HTTPS functions. The permission ${cli_color_1.bold(PERMISSION)} is required to deploy the following functions:\n\n- ` +
|
|
54
|
-
|
|
54
|
+
newHttpsEndpoints.map((func) => func.id).join("\n- ") +
|
|
55
55
|
`\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
|
|
56
56
|
}
|
|
57
57
|
logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
|
|
@@ -11,6 +11,7 @@ const gcs = require("../../gcp/storage");
|
|
|
11
11
|
const gcf = require("../../gcp/cloudfunctions");
|
|
12
12
|
const gcfv2 = require("../../gcp/cloudfunctionsv2");
|
|
13
13
|
const utils = require("../../utils");
|
|
14
|
+
const backend = require("./backend");
|
|
14
15
|
const GCP_REGION = api_1.functionsUploadRegion;
|
|
15
16
|
tmp_1.setGracefulCleanup();
|
|
16
17
|
async function uploadSourceV1(context) {
|
|
@@ -44,18 +45,11 @@ async function deploy(context, options, payload) {
|
|
|
44
45
|
try {
|
|
45
46
|
const want = payload.functions.backend;
|
|
46
47
|
const uploads = [];
|
|
47
|
-
if (want.
|
|
48
|
+
if (backend.allEndpoints(want).some((endpoint) => endpoint.platform === "gcfv1")) {
|
|
48
49
|
uploads.push(uploadSourceV1(context));
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const regions = [];
|
|
53
|
-
for (const func of functions) {
|
|
54
|
-
if (func.platform === "gcfv2" && -1 === regions.indexOf(func.region)) {
|
|
55
|
-
regions.push(func.region);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
for (const region of regions) {
|
|
51
|
+
for (const region of Object.keys(want.endpoints)) {
|
|
52
|
+
if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) {
|
|
59
53
|
uploads.push(uploadSourceV2(context, region));
|
|
60
54
|
}
|
|
61
55
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const clc = require("cli-color");
|
|
5
|
-
const logger_1 = require("../../logger");
|
|
6
|
-
const backend = require("./backend");
|
|
7
|
-
const track = require("../../track");
|
|
8
|
-
const utils = require("../../utils");
|
|
3
|
+
exports.getFunctionLabel = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
|
|
9
4
|
function functionMatchesAnyGroup(func, filterGroups) {
|
|
10
5
|
if (!filterGroups.length) {
|
|
11
6
|
return true;
|
|
@@ -40,67 +35,7 @@ function getFilterGroups(options) {
|
|
|
40
35
|
});
|
|
41
36
|
}
|
|
42
37
|
exports.getFilterGroups = getFilterGroups;
|
|
43
|
-
function
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
exports.getFunctionId = getFunctionId;
|
|
47
|
-
function getRegion(fullName) {
|
|
48
|
-
return fullName.split("/")[3];
|
|
49
|
-
}
|
|
50
|
-
function getFunctionLabel(fnOrName) {
|
|
51
|
-
if (typeof fnOrName === "string") {
|
|
52
|
-
return getFunctionId(fnOrName) + "(" + getRegion(fnOrName) + ")";
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
return `${fnOrName.id}(${fnOrName.region})`;
|
|
56
|
-
}
|
|
38
|
+
function getFunctionLabel(fn) {
|
|
39
|
+
return `${fn.id}(${fn.region})`;
|
|
57
40
|
}
|
|
58
41
|
exports.getFunctionLabel = getFunctionLabel;
|
|
59
|
-
function logAndTrackDeployStats(queue, errorHandler) {
|
|
60
|
-
const stats = queue.stats();
|
|
61
|
-
logger_1.logger.debug(`Total Function Deployment time: ${stats.elapsed}`);
|
|
62
|
-
logger_1.logger.debug(`${stats.total} Functions Deployed`);
|
|
63
|
-
logger_1.logger.debug(`${errorHandler.errors.length} Functions Errored`);
|
|
64
|
-
logger_1.logger.debug(`Average Function Deployment time: ${stats.avg}`);
|
|
65
|
-
if (stats.total > 0) {
|
|
66
|
-
if (errorHandler.errors.length === 0) {
|
|
67
|
-
track("functions_deploy_result", "success", stats.total);
|
|
68
|
-
}
|
|
69
|
-
else if (errorHandler.errors.length < stats.total) {
|
|
70
|
-
track("functions_deploy_result", "partial_success", stats.total - errorHandler.errors.length);
|
|
71
|
-
track("functions_deploy_result", "partial_failure", errorHandler.errors.length);
|
|
72
|
-
track("functions_deploy_result", "partial_error_ratio", errorHandler.errors.length / stats.total);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
track("functions_deploy_result", "failure", stats.total);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
exports.logAndTrackDeployStats = logAndTrackDeployStats;
|
|
80
|
-
function printSuccess(func, type) {
|
|
81
|
-
utils.logSuccess(clc.bold.green("functions[" + getFunctionLabel(func) + "]: ") +
|
|
82
|
-
"Successful " +
|
|
83
|
-
type +
|
|
84
|
-
" operation. ");
|
|
85
|
-
}
|
|
86
|
-
exports.printSuccess = printSuccess;
|
|
87
|
-
async function printTriggerUrls(context, want) {
|
|
88
|
-
const have = await backend.existingBackend(context, true);
|
|
89
|
-
const httpsFunctions = have.cloudFunctions.filter((fn) => {
|
|
90
|
-
if (backend.isEventTrigger(fn.trigger)) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return want.cloudFunctions.some(backend.sameFunctionName(fn));
|
|
94
|
-
});
|
|
95
|
-
if (httpsFunctions.length === 0) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
for (const httpsFunc of httpsFunctions) {
|
|
99
|
-
if (!httpsFunc.uri) {
|
|
100
|
-
logger_1.logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
logger_1.logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
exports.printTriggerUrls = printTriggerUrls;
|