firebase-tools 10.2.2 → 10.4.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/deploy.js +1 -1
- package/lib/commands/experimental-functions-shell.js +1 -1
- package/lib/commands/ext-configure.js +68 -7
- package/lib/commands/ext-export.js +10 -9
- package/lib/commands/ext-install.js +73 -9
- package/lib/commands/ext-uninstall.js +9 -0
- package/lib/commands/ext-update.js +58 -3
- package/lib/commands/functions-config-export.js +2 -2
- package/lib/commands/functions-shell.js +1 -1
- package/lib/commands/hosting-channel-create.js +2 -2
- package/lib/commands/hosting-channel-delete.js +2 -2
- package/lib/commands/hosting-channel-deploy.js +2 -2
- package/lib/commands/hosting-channel-list.js +2 -2
- package/lib/commands/hosting-channel-open.js +2 -2
- package/lib/commands/hosting-sites-delete.js +2 -2
- package/lib/commands/serve.js +1 -1
- package/lib/commands/target-apply.js +2 -2
- package/lib/commands/target-clear.js +2 -2
- package/lib/commands/target-remove.js +2 -2
- package/lib/commands/target.js +2 -2
- package/lib/config.js +9 -3
- package/lib/deploy/extensions/planner.js +15 -9
- package/lib/deploy/functions/backend.js +10 -1
- package/lib/deploy/functions/checkIam.js +4 -4
- package/lib/deploy/functions/prepare.js +2 -1
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/planner.js +34 -20
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
- package/lib/deploy/functions/runtimes/node/index.js +27 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
- package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
- package/lib/deploy/functions/services/index.js +9 -1
- package/lib/deploy/functions/services/storage.js +10 -4
- package/lib/deploy/functions/triggerRegionHelper.js +1 -1
- package/lib/emulator/auth/apiSpec.js +37 -0
- package/lib/emulator/commandUtils.js +2 -2
- package/lib/emulator/constants.js +1 -0
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/extensions/validation.js +37 -2
- package/lib/emulator/extensionsEmulator.js +47 -9
- package/lib/emulator/functionsEmulator.js +17 -12
- package/lib/emulator/functionsEmulatorShared.js +34 -11
- package/lib/emulator/storage/apis/firebase.js +316 -341
- package/lib/emulator/storage/apis/gcloud.js +238 -113
- package/lib/emulator/storage/crc.js +5 -1
- package/lib/emulator/storage/errors.js +9 -0
- package/lib/emulator/storage/files.js +161 -304
- package/lib/emulator/storage/index.js +25 -74
- package/lib/emulator/storage/metadata.js +63 -49
- package/lib/emulator/storage/multipart.js +62 -0
- package/lib/emulator/storage/persistence.js +78 -0
- package/lib/emulator/storage/rules/config.js +34 -0
- package/lib/emulator/storage/rules/manager.js +98 -0
- package/lib/emulator/storage/rules/runtime.js +4 -0
- package/lib/emulator/storage/rules/utils.js +48 -0
- package/lib/emulator/storage/server.js +2 -2
- package/lib/emulator/storage/upload.js +106 -0
- package/lib/extensions/askUserForParam.js +77 -28
- package/lib/extensions/emulator/optionsHelper.js +35 -3
- package/lib/extensions/extensionsHelper.js +19 -10
- package/lib/extensions/manifest.js +142 -14
- package/lib/extensions/paramHelper.js +32 -9
- package/lib/fsutils.js +14 -1
- package/lib/functions/env.js +4 -6
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +20 -7
- package/lib/gcp/cloudfunctionsv2.js +30 -12
- package/lib/gcp/resourceManager.js +4 -4
- package/lib/requireConfig.js +11 -9
- package/lib/serve/functions.js +2 -1
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/deploy/extensions/params.js +0 -42
- package/lib/deploy/functions/eventTypes.js +0 -10
- package/lib/prepareUpload.js +0 -44
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const clc = require("cli-color");
|
|
4
4
|
const command_1 = require("../command");
|
|
5
5
|
const logger_1 = require("../logger");
|
|
6
|
-
const
|
|
6
|
+
const requireConfig_1 = require("../requireConfig");
|
|
7
7
|
const utils = require("../utils");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
9
|
exports.default = new command_1.Command("target:apply <type> <name> <resources...>")
|
|
10
10
|
.description("apply a deploy target to a resource")
|
|
11
|
-
.before(requireConfig)
|
|
11
|
+
.before(requireConfig_1.requireConfig)
|
|
12
12
|
.action((type, name, resources, options) => {
|
|
13
13
|
if (!options.project) {
|
|
14
14
|
throw new error_1.FirebaseError(`Must have an active project to set deploy targets. Try ${clc.bold("firebase use --add")}`);
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const clc = require("cli-color");
|
|
4
4
|
const command_1 = require("../command");
|
|
5
|
-
const
|
|
5
|
+
const requireConfig_1 = require("../requireConfig");
|
|
6
6
|
const utils = require("../utils");
|
|
7
7
|
exports.default = new command_1.Command("target:clear <type> <target>")
|
|
8
8
|
.description("clear all resources from a named resource target")
|
|
9
|
-
.before(requireConfig)
|
|
9
|
+
.before(requireConfig_1.requireConfig)
|
|
10
10
|
.action((type, name, options) => {
|
|
11
11
|
const existed = options.rc.clearTarget(options.project, type, name);
|
|
12
12
|
if (existed) {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const clc = require("cli-color");
|
|
4
4
|
const command_1 = require("../command");
|
|
5
|
-
const
|
|
5
|
+
const requireConfig_1 = require("../requireConfig");
|
|
6
6
|
const utils = require("../utils");
|
|
7
7
|
exports.default = new command_1.Command("target:remove <type> <resource>")
|
|
8
8
|
.description("remove a resource target")
|
|
9
|
-
.before(requireConfig)
|
|
9
|
+
.before(requireConfig_1.requireConfig)
|
|
10
10
|
.action((type, resource, options) => {
|
|
11
11
|
const name = options.rc.removeTarget(options.project, type, resource);
|
|
12
12
|
if (name) {
|
package/lib/commands/target.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const clc = require("cli-color");
|
|
4
4
|
const command_1 = require("../command");
|
|
5
5
|
const logger_1 = require("../logger");
|
|
6
|
-
const
|
|
6
|
+
const requireConfig_1 = require("../requireConfig");
|
|
7
7
|
const utils = require("../utils");
|
|
8
8
|
function logTargets(type, targets) {
|
|
9
9
|
logger_1.logger.info(clc.cyan("[ " + type + " ]"));
|
|
@@ -13,7 +13,7 @@ function logTargets(type, targets) {
|
|
|
13
13
|
}
|
|
14
14
|
exports.default = new command_1.Command("target [type]")
|
|
15
15
|
.description("display configured deploy targets for the current project")
|
|
16
|
-
.before(requireConfig)
|
|
16
|
+
.before(requireConfig_1.requireConfig)
|
|
17
17
|
.action((type, options) => {
|
|
18
18
|
if (!options.project) {
|
|
19
19
|
return utils.reject("No active project, cannot list deploy targets.");
|
package/lib/config.js
CHANGED
|
@@ -17,12 +17,12 @@ const logger_1 = require("./logger");
|
|
|
17
17
|
const loadCJSON = require("./loadCJSON");
|
|
18
18
|
const parseBoltRules = require("./parseBoltRules");
|
|
19
19
|
class Config {
|
|
20
|
-
constructor(src, options) {
|
|
20
|
+
constructor(src, options = {}) {
|
|
21
21
|
this.data = {};
|
|
22
22
|
this.defaults = {};
|
|
23
23
|
this.notes = {};
|
|
24
|
-
this.options = options
|
|
25
|
-
this.projectDir = options.projectDir || (0, detectProjectRoot_1.detectProjectRoot)(options);
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.projectDir = this.options.projectDir || (0, detectProjectRoot_1.detectProjectRoot)(this.options);
|
|
26
26
|
this._src = src;
|
|
27
27
|
if (this._src.firebase) {
|
|
28
28
|
this.defaults.project = this._src.firebase;
|
|
@@ -144,6 +144,12 @@ class Config {
|
|
|
144
144
|
fs.ensureFileSync(this.path(p));
|
|
145
145
|
fs.writeFileSync(this.path(p), content, "utf8");
|
|
146
146
|
}
|
|
147
|
+
projectFileExists(p) {
|
|
148
|
+
return fs.existsSync(this.path(p));
|
|
149
|
+
}
|
|
150
|
+
deleteProjectFile(p) {
|
|
151
|
+
fs.removeSync(this.path(p));
|
|
152
|
+
}
|
|
147
153
|
askWriteProjectFile(p, content, force) {
|
|
148
154
|
const writeTo = this.path(p);
|
|
149
155
|
let next;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveVersion = exports.want = exports.have = exports.getExtension = exports.getExtensionVersion = void 0;
|
|
4
4
|
const semver = require("semver");
|
|
5
|
-
const error_1 = require("../../error");
|
|
6
5
|
const extensionsApi = require("../../extensions/extensionsApi");
|
|
7
|
-
const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
8
6
|
const refs = require("../../extensions/refs");
|
|
9
|
-
const
|
|
7
|
+
const error_1 = require("../../error");
|
|
8
|
+
const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
10
9
|
const logger_1 = require("../../logger");
|
|
10
|
+
const manifest_1 = require("../../extensions/manifest");
|
|
11
11
|
async function getExtensionVersion(i) {
|
|
12
12
|
if (!i.extensionVersion) {
|
|
13
13
|
if (!i.ref) {
|
|
@@ -52,15 +52,15 @@ async function want(args) {
|
|
|
52
52
|
const instanceId = e[0];
|
|
53
53
|
const ref = refs.parse(e[1]);
|
|
54
54
|
ref.version = await resolveVersion(ref);
|
|
55
|
-
const params = (0,
|
|
55
|
+
const params = (0, manifest_1.readInstanceParam)({
|
|
56
56
|
projectDir: args.projectDir,
|
|
57
57
|
instanceId,
|
|
58
58
|
projectId: args.projectId,
|
|
59
59
|
projectNumber: args.projectNumber,
|
|
60
60
|
aliases: args.aliases,
|
|
61
|
-
checkLocal: args.
|
|
61
|
+
checkLocal: args.emulatorMode,
|
|
62
62
|
});
|
|
63
|
-
const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
|
|
63
|
+
const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId, args.emulatorMode);
|
|
64
64
|
const subbedParams = (0, extensionsHelper_1.substituteParams)(params, autoPopulatedParams);
|
|
65
65
|
instanceSpecs.push({
|
|
66
66
|
instanceId,
|
|
@@ -81,11 +81,17 @@ async function want(args) {
|
|
|
81
81
|
}
|
|
82
82
|
exports.want = want;
|
|
83
83
|
async function resolveVersion(ref) {
|
|
84
|
-
if (!ref.version || ref.version === "latest") {
|
|
85
|
-
return "latest";
|
|
86
|
-
}
|
|
87
84
|
const extensionRef = refs.toExtensionRef(ref);
|
|
88
85
|
const versions = await extensionsApi.listExtensionVersions(extensionRef);
|
|
86
|
+
if (versions.length === 0) {
|
|
87
|
+
throw new error_1.FirebaseError(`No versions found for ${extensionRef}`);
|
|
88
|
+
}
|
|
89
|
+
if (!ref.version || ref.version === "latest") {
|
|
90
|
+
return versions
|
|
91
|
+
.map((ev) => ev.spec.version)
|
|
92
|
+
.sort(semver.compare)
|
|
93
|
+
.pop();
|
|
94
|
+
}
|
|
89
95
|
const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
|
|
90
96
|
if (!maxSatisfying) {
|
|
91
97
|
throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
|
|
3
|
+
exports.compareFunctions = exports.findEventFilter = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = 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");
|
|
@@ -42,6 +42,11 @@ exports.memoryOptionDisplayName = memoryOptionDisplayName;
|
|
|
42
42
|
exports.DEFAULT_MEMORY = 256;
|
|
43
43
|
exports.MIN_MEMORY_FOR_CONCURRENCY = 2048;
|
|
44
44
|
exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
|
|
45
|
+
function secretVersionName(s) {
|
|
46
|
+
var _a;
|
|
47
|
+
return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
|
|
48
|
+
}
|
|
49
|
+
exports.secretVersionName = secretVersionName;
|
|
45
50
|
function isHttpsTriggered(triggered) {
|
|
46
51
|
return {}.hasOwnProperty.call(triggered, "httpsTrigger");
|
|
47
52
|
}
|
|
@@ -225,6 +230,10 @@ const missingEndpoint = (backend) => (endpoint) => {
|
|
|
225
230
|
return !(0, exports.hasEndpoint)(backend)(endpoint);
|
|
226
231
|
};
|
|
227
232
|
exports.missingEndpoint = missingEndpoint;
|
|
233
|
+
function findEventFilter(endpoint, attribute) {
|
|
234
|
+
return endpoint.eventTrigger.eventFilters.find((ef) => ef.attribute === attribute);
|
|
235
|
+
}
|
|
236
|
+
exports.findEventFilter = findEventFilter;
|
|
228
237
|
function compareFunctions(left, right) {
|
|
229
238
|
if (left.platform !== right.platform) {
|
|
230
239
|
return right.platform < left.platform ? -1 : 1;
|
|
@@ -87,7 +87,7 @@ function mergeBindings(policy, allRequiredBindings) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
exports.mergeBindings = mergeBindings;
|
|
90
|
-
async function ensureServiceAgentRoles(
|
|
90
|
+
async function ensureServiceAgentRoles(projectNumber, want, have) {
|
|
91
91
|
const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
|
|
92
92
|
const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
|
|
93
93
|
const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
|
|
@@ -96,7 +96,7 @@ async function ensureServiceAgentRoles(projectId, want, have) {
|
|
|
96
96
|
}
|
|
97
97
|
let policy;
|
|
98
98
|
try {
|
|
99
|
-
policy = await (0, resourceManager_1.getIamPolicy)(
|
|
99
|
+
policy = await (0, resourceManager_1.getIamPolicy)(projectNumber);
|
|
100
100
|
}
|
|
101
101
|
catch (err) {
|
|
102
102
|
utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
|
|
@@ -105,11 +105,11 @@ async function ensureServiceAgentRoles(projectId, want, have) {
|
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
const findRequiredBindings = [];
|
|
108
|
-
newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(
|
|
108
|
+
newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectNumber, policy)));
|
|
109
109
|
const allRequiredBindings = await Promise.all(findRequiredBindings);
|
|
110
110
|
mergeBindings(policy, allRequiredBindings);
|
|
111
111
|
try {
|
|
112
|
-
await (0, resourceManager_1.setIamPolicy)(
|
|
112
|
+
await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
|
|
113
113
|
}
|
|
114
114
|
catch (err) {
|
|
115
115
|
throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
|
|
@@ -27,6 +27,7 @@ function hasDotenv(opts) {
|
|
|
27
27
|
}
|
|
28
28
|
async function prepare(context, options, payload) {
|
|
29
29
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
30
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
30
31
|
const sourceDirName = options.config.get("functions.source");
|
|
31
32
|
if (!sourceDirName) {
|
|
32
33
|
throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions.source defined in firebase.json`);
|
|
@@ -111,7 +112,7 @@ async function prepare(context, options, payload) {
|
|
|
111
112
|
return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, context.filters);
|
|
112
113
|
});
|
|
113
114
|
const haveBackend = await backend.existingBackend(context);
|
|
114
|
-
await (0, checkIam_1.ensureServiceAgentRoles)(
|
|
115
|
+
await (0, checkIam_1.ensureServiceAgentRoles)(projectNumber, wantBackend, haveBackend);
|
|
115
116
|
inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
|
|
116
117
|
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
117
118
|
await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
|
|
@@ -51,12 +51,12 @@ class Fabricator {
|
|
|
51
51
|
totalTime: 0,
|
|
52
52
|
results: [],
|
|
53
53
|
};
|
|
54
|
-
const
|
|
55
|
-
const results = await this.
|
|
54
|
+
const deployChangesets = Object.values(plan).map(async (changes) => {
|
|
55
|
+
const results = await this.applyChangeset(changes);
|
|
56
56
|
summary.results.push(...results);
|
|
57
57
|
return;
|
|
58
58
|
});
|
|
59
|
-
const promiseResults = await utils.allSettled(
|
|
59
|
+
const promiseResults = await utils.allSettled(deployChangesets);
|
|
60
60
|
const errs = promiseResults
|
|
61
61
|
.filter((r) => r.status === "rejected")
|
|
62
62
|
.map((r) => r.reason);
|
|
@@ -66,7 +66,7 @@ class Fabricator {
|
|
|
66
66
|
summary.totalTime = timer.stop();
|
|
67
67
|
return summary;
|
|
68
68
|
}
|
|
69
|
-
async
|
|
69
|
+
async applyChangeset(changes) {
|
|
70
70
|
const deployResults = [];
|
|
71
71
|
const handle = async (op, endpoint, fn) => {
|
|
72
72
|
const timer = new timer_1.Timer();
|
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.
|
|
3
|
+
exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0;
|
|
4
4
|
const functionsDeployHelper_1 = require("../functionsDeployHelper");
|
|
5
|
-
const functionsDeployHelper_2 = require("../functionsDeployHelper");
|
|
6
5
|
const deploymentTool_1 = require("../../../deploymentTool");
|
|
7
6
|
const error_1 = require("../../../error");
|
|
8
7
|
const utils = require("../../../utils");
|
|
9
8
|
const backend = require("../backend");
|
|
10
|
-
const
|
|
11
|
-
function
|
|
12
|
-
const
|
|
9
|
+
const v2events = require("../../../functions/events/v2");
|
|
10
|
+
function calculateChangesets(want, have, keyFn, options) {
|
|
11
|
+
const toCreate = utils.groupBy(Object.keys(want)
|
|
13
12
|
.filter((id) => !have[id])
|
|
14
|
-
.map((id) => want[id]);
|
|
15
|
-
const
|
|
13
|
+
.map((id) => want[id]), keyFn);
|
|
14
|
+
const toDelete = utils.groupBy(Object.keys(have)
|
|
16
15
|
.filter((id) => !want[id])
|
|
17
16
|
.filter((id) => options.deleteAll || (0, deploymentTool_1.isFirebaseManaged)(have[id].labels || {}))
|
|
18
|
-
.map((id) => have[id]);
|
|
19
|
-
const
|
|
17
|
+
.map((id) => have[id]), keyFn);
|
|
18
|
+
const toUpdate = utils.groupBy(Object.keys(want)
|
|
20
19
|
.filter((id) => have[id])
|
|
21
|
-
.map((id) => calculateUpdate(want[id], have[id]));
|
|
22
|
-
|
|
20
|
+
.map((id) => calculateUpdate(want[id], have[id])), (eu) => keyFn(eu.endpoint));
|
|
21
|
+
const result = {};
|
|
22
|
+
const keys = new Set([
|
|
23
|
+
...Object.keys(toCreate),
|
|
24
|
+
...Object.keys(toDelete),
|
|
25
|
+
...Object.keys(toUpdate),
|
|
26
|
+
]);
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
result[key] = {
|
|
29
|
+
endpointsToCreate: toCreate[key] || [],
|
|
30
|
+
endpointsToUpdate: toUpdate[key] || [],
|
|
31
|
+
endpointsToDelete: toDelete[key] || [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
23
35
|
}
|
|
24
|
-
exports.
|
|
36
|
+
exports.calculateChangesets = calculateChangesets;
|
|
25
37
|
function calculateUpdate(want, have) {
|
|
26
38
|
checkForIllegalUpdate(want, have);
|
|
27
39
|
const update = {
|
|
@@ -37,7 +49,7 @@ function calculateUpdate(want, have) {
|
|
|
37
49
|
}
|
|
38
50
|
exports.calculateUpdate = calculateUpdate;
|
|
39
51
|
function createDeploymentPlan(want, have, options = {}) {
|
|
40
|
-
|
|
52
|
+
let deployment = {};
|
|
41
53
|
want = backend.matchingBackend(want, (endpoint) => {
|
|
42
54
|
return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, options.filters || []);
|
|
43
55
|
});
|
|
@@ -46,7 +58,8 @@ function createDeploymentPlan(want, have, options = {}) {
|
|
|
46
58
|
});
|
|
47
59
|
const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]);
|
|
48
60
|
for (const region of regions) {
|
|
49
|
-
|
|
61
|
+
const changesets = calculateChangesets(want.endpoints[region] || {}, have.endpoints[region] || {}, (e) => `${e.region}-${e.availableMemoryMb || "default"}`, options);
|
|
62
|
+
deployment = Object.assign(Object.assign({}, deployment), changesets);
|
|
50
63
|
}
|
|
51
64
|
if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) {
|
|
52
65
|
utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
|
|
@@ -90,6 +103,7 @@ function changedTriggerRegion(want, have) {
|
|
|
90
103
|
}
|
|
91
104
|
exports.changedTriggerRegion = changedTriggerRegion;
|
|
92
105
|
function changedV2PubSubTopic(want, have) {
|
|
106
|
+
var _a, _b;
|
|
93
107
|
if (want.platform !== "gcfv2") {
|
|
94
108
|
return false;
|
|
95
109
|
}
|
|
@@ -102,13 +116,13 @@ function changedV2PubSubTopic(want, have) {
|
|
|
102
116
|
if (!backend.isEventTriggered(have)) {
|
|
103
117
|
return false;
|
|
104
118
|
}
|
|
105
|
-
if (want.eventTrigger.eventType !==
|
|
119
|
+
if (want.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
|
|
106
120
|
return false;
|
|
107
121
|
}
|
|
108
|
-
if (have.eventTrigger.eventType !==
|
|
122
|
+
if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
|
|
109
123
|
return false;
|
|
110
124
|
}
|
|
111
|
-
return have
|
|
125
|
+
return (((_a = backend.findEventFilter(have, "topic")) === null || _a === void 0 ? void 0 : _a.value) !== ((_b = backend.findEventFilter(want, "topic")) === null || _b === void 0 ? void 0 : _b.value));
|
|
112
126
|
}
|
|
113
127
|
exports.changedV2PubSubTopic = changedV2PubSubTopic;
|
|
114
128
|
function upgradedScheduleFromV1ToV2(want, have) {
|
|
@@ -149,17 +163,17 @@ function checkForIllegalUpdate(want, have) {
|
|
|
149
163
|
const wantType = triggerType(want);
|
|
150
164
|
const haveType = triggerType(have);
|
|
151
165
|
if (wantType !== haveType) {
|
|
152
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
166
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
|
|
153
167
|
}
|
|
154
168
|
if (want.platform === "gcfv1" && have.platform === "gcfv2") {
|
|
155
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
169
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
|
|
156
170
|
}
|
|
157
171
|
exports.checkForV2Upgrade(want, have);
|
|
158
172
|
}
|
|
159
173
|
exports.checkForIllegalUpdate = checkForIllegalUpdate;
|
|
160
174
|
function checkForV2Upgrade(want, have) {
|
|
161
175
|
if (want.platform === "gcfv2" && have.platform === "gcfv1") {
|
|
162
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
176
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.`);
|
|
163
177
|
}
|
|
164
178
|
}
|
|
165
179
|
exports.checkForV2Upgrade = checkForV2Upgrade;
|
|
@@ -88,13 +88,18 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
|
|
|
88
88
|
if (backend.isEventTriggered(ep)) {
|
|
89
89
|
(0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
|
|
90
90
|
(0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
|
|
91
|
-
eventFilters: "
|
|
91
|
+
eventFilters: "array",
|
|
92
92
|
eventType: "string",
|
|
93
93
|
retry: "boolean",
|
|
94
94
|
region: "string",
|
|
95
95
|
serviceAccountEmail: "string",
|
|
96
96
|
});
|
|
97
97
|
triggered = { eventTrigger: ep.eventTrigger };
|
|
98
|
+
for (const eventFilter of triggered.eventTrigger.eventFilters) {
|
|
99
|
+
if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) {
|
|
100
|
+
eventFilter.value = `projects/${project}/topics/${eventFilter.value}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
else if (backend.isHttpsTriggered(ep)) {
|
|
100
105
|
(0, parsing_1.assertKeyTypes)(prefix + ".httpsTrigger", ep.httpsTrigger, {
|
|
@@ -4,14 +4,20 @@ exports.Delegate = exports.tryCreateDelegate = void 0;
|
|
|
4
4
|
const util_1 = require("util");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const portfinder = require("portfinder");
|
|
8
|
+
const semver = require("semver");
|
|
7
9
|
const spawn = require("cross-spawn");
|
|
8
10
|
const node_fetch_1 = require("node-fetch");
|
|
9
11
|
const error_1 = require("../../../../error");
|
|
10
12
|
const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
|
|
11
13
|
const logger_1 = require("../../../../logger");
|
|
14
|
+
const previews_1 = require("../../../../previews");
|
|
15
|
+
const utils_1 = require("../../../../utils");
|
|
16
|
+
const discovery = require("../discovery");
|
|
12
17
|
const validate = require("./validate");
|
|
13
18
|
const versioning = require("./versioning");
|
|
14
19
|
const parseTriggers = require("./parseTriggers");
|
|
20
|
+
const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
|
|
15
21
|
async function tryCreateDelegate(context) {
|
|
16
22
|
const packageJsonPath = path.join(context.sourceDir, "package.json");
|
|
17
23
|
if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
|
|
@@ -77,6 +83,27 @@ class Delegate {
|
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
async discoverSpec(config, env) {
|
|
86
|
+
if (previews_1.previews.functionsv2) {
|
|
87
|
+
if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
|
|
88
|
+
(0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
|
|
89
|
+
`Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
|
|
90
|
+
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
91
|
+
}
|
|
92
|
+
let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
|
|
93
|
+
if (!discovered) {
|
|
94
|
+
const getPort = (0, util_1.promisify)(portfinder.getPort);
|
|
95
|
+
const port = await getPort();
|
|
96
|
+
const kill = await this.serve(port, env);
|
|
97
|
+
try {
|
|
98
|
+
discovered = await discovery.detectFromPort(port, this.projectId, this.runtime);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await kill();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
discovered.environmentVariables = env;
|
|
105
|
+
return discovered;
|
|
106
|
+
}
|
|
80
107
|
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
81
108
|
}
|
|
82
109
|
}
|
|
@@ -9,7 +9,7 @@ const logger_1 = require("../../../../logger");
|
|
|
9
9
|
const backend = require("../../backend");
|
|
10
10
|
const api = require("../../../../api");
|
|
11
11
|
const proto = require("../../../../gcp/proto");
|
|
12
|
-
const
|
|
12
|
+
const v2events = require("../../../../functions/events/v2");
|
|
13
13
|
const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
|
|
14
14
|
function removeInspectOptions(options) {
|
|
15
15
|
return options.filter((opt) => !opt.startsWith("--inspect"));
|
|
@@ -73,6 +73,7 @@ function mergeRequiredAPIs(backend) {
|
|
|
73
73
|
}
|
|
74
74
|
exports.mergeRequiredAPIs = mergeRequiredAPIs;
|
|
75
75
|
function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
76
|
+
var _a;
|
|
76
77
|
Object.freeze(annotation);
|
|
77
78
|
for (const region of annotation.regions || [api.functionsDefaultRegion]) {
|
|
78
79
|
let triggered;
|
|
@@ -88,12 +89,18 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
else if (annotation.httpsTrigger) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
if ((_a = annotation.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
|
|
93
|
+
delete annotation.labels["deployment-callable"];
|
|
94
|
+
triggered = { callableTrigger: {} };
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const trigger = {};
|
|
98
|
+
if (annotation.failurePolicy) {
|
|
99
|
+
logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
|
|
100
|
+
}
|
|
101
|
+
proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker");
|
|
102
|
+
triggered = { httpsTrigger: trigger };
|
|
94
103
|
}
|
|
95
|
-
proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker");
|
|
96
|
-
triggered = { httpsTrigger: trigger };
|
|
97
104
|
}
|
|
98
105
|
else if (annotation.schedule) {
|
|
99
106
|
want.requiredAPIs.push({
|
|
@@ -106,16 +113,32 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
|
106
113
|
triggered = {
|
|
107
114
|
eventTrigger: {
|
|
108
115
|
eventType: annotation.eventTrigger.eventType,
|
|
109
|
-
eventFilters:
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
eventFilters: [
|
|
117
|
+
{
|
|
118
|
+
attribute: "resource",
|
|
119
|
+
value: annotation.eventTrigger.resource,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
112
122
|
retry: !!annotation.failurePolicy,
|
|
113
123
|
},
|
|
114
124
|
};
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
if (annotation.platform === "gcfv2") {
|
|
126
|
+
if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
|
|
127
|
+
triggered.eventTrigger.eventFilters = [
|
|
128
|
+
{
|
|
129
|
+
attribute: "topic",
|
|
130
|
+
value: annotation.eventTrigger.resource,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
|
|
135
|
+
triggered.eventTrigger.eventFilters = [
|
|
136
|
+
{
|
|
137
|
+
attribute: "bucket",
|
|
138
|
+
value: annotation.eventTrigger.resource,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", id: annotation.name, region: region, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureFirebaseAlertsTriggerRegion = exports.obtainFirebaseAlertsBindings = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
|
|
4
|
+
const error_1 = require("../../../error");
|
|
5
|
+
exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
|
|
6
|
+
function obtainFirebaseAlertsBindings(projectNumber, existingPolicy) {
|
|
7
|
+
const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
8
|
+
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE);
|
|
9
|
+
if (!pubsubBinding) {
|
|
10
|
+
pubsubBinding = {
|
|
11
|
+
role: exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
|
|
12
|
+
members: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (!pubsubBinding.members.find((m) => m === pubsubServiceAgent)) {
|
|
16
|
+
pubsubBinding.members.push(pubsubServiceAgent);
|
|
17
|
+
}
|
|
18
|
+
return Promise.resolve([pubsubBinding]);
|
|
19
|
+
}
|
|
20
|
+
exports.obtainFirebaseAlertsBindings = obtainFirebaseAlertsBindings;
|
|
21
|
+
function ensureFirebaseAlertsTriggerRegion(endpoint) {
|
|
22
|
+
if (!endpoint.eventTrigger.region) {
|
|
23
|
+
endpoint.eventTrigger.region = "global";
|
|
24
|
+
}
|
|
25
|
+
if (endpoint.eventTrigger.region !== "global") {
|
|
26
|
+
throw new error_1.FirebaseError("A firebase alerts trigger must specify 'global' trigger location");
|
|
27
|
+
}
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
exports.ensureFirebaseAlertsTriggerRegion = ensureFirebaseAlertsTriggerRegion;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
3
|
+
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.FirebaseAlertsService = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
4
4
|
const backend = require("../backend");
|
|
5
5
|
const storage_1 = require("./storage");
|
|
6
|
+
const firebaseAlerts_1 = require("./firebaseAlerts");
|
|
6
7
|
const noop = () => Promise.resolve();
|
|
7
8
|
exports.NoOpService = {
|
|
8
9
|
name: "noop",
|
|
@@ -22,12 +23,19 @@ exports.StorageService = {
|
|
|
22
23
|
requiredProjectBindings: storage_1.obtainStorageBindings,
|
|
23
24
|
ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
|
|
24
25
|
};
|
|
26
|
+
exports.FirebaseAlertsService = {
|
|
27
|
+
name: "firebasealerts",
|
|
28
|
+
api: "logging.googleapis.com",
|
|
29
|
+
requiredProjectBindings: firebaseAlerts_1.obtainFirebaseAlertsBindings,
|
|
30
|
+
ensureTriggerRegion: firebaseAlerts_1.ensureFirebaseAlertsTriggerRegion,
|
|
31
|
+
};
|
|
25
32
|
exports.EVENT_SERVICE_MAPPING = {
|
|
26
33
|
"google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
|
|
27
34
|
"google.cloud.storage.object.v1.finalized": exports.StorageService,
|
|
28
35
|
"google.cloud.storage.object.v1.archived": exports.StorageService,
|
|
29
36
|
"google.cloud.storage.object.v1.deleted": exports.StorageService,
|
|
30
37
|
"google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
|
|
38
|
+
"google.firebase.firebasealerts.alerts.v1.published": exports.FirebaseAlertsService,
|
|
31
39
|
};
|
|
32
40
|
function serviceForEndpoint(endpoint) {
|
|
33
41
|
if (!backend.isEventTriggered(endpoint)) {
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
|
|
4
4
|
const storage = require("../../../gcp/storage");
|
|
5
|
+
const backend = require("../backend");
|
|
5
6
|
const logger_1 = require("../../../logger");
|
|
6
7
|
const error_1 = require("../../../error");
|
|
7
8
|
const location_1 = require("../../../gcp/location");
|
|
8
9
|
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
9
|
-
async function obtainStorageBindings(
|
|
10
|
-
const storageResponse = await storage.getServiceAccount(
|
|
10
|
+
async function obtainStorageBindings(projectNumber, existingPolicy) {
|
|
11
|
+
const storageResponse = await storage.getServiceAccount(projectNumber);
|
|
11
12
|
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
12
13
|
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
|
|
13
14
|
if (!pubsubBinding) {
|
|
@@ -22,11 +23,16 @@ async function obtainStorageBindings(projectId, existingPolicy) {
|
|
|
22
23
|
return [pubsubBinding];
|
|
23
24
|
}
|
|
24
25
|
exports.obtainStorageBindings = obtainStorageBindings;
|
|
25
|
-
async function ensureStorageTriggerRegion(endpoint
|
|
26
|
+
async function ensureStorageTriggerRegion(endpoint) {
|
|
27
|
+
const { eventTrigger } = endpoint;
|
|
26
28
|
if (!eventTrigger.region) {
|
|
27
29
|
logger_1.logger.debug("Looking up bucket region for the storage event trigger");
|
|
30
|
+
const bucketFilter = backend.findEventFilter(endpoint, "bucket");
|
|
31
|
+
if (!bucketFilter) {
|
|
32
|
+
throw new error_1.FirebaseError("Storage event trigger unexpectedly missing event filter with bucket attribute.");
|
|
33
|
+
}
|
|
28
34
|
try {
|
|
29
|
-
const bucket = await storage.getBucket(
|
|
35
|
+
const bucket = await storage.getBucket(bucketFilter.value);
|
|
30
36
|
eventTrigger.region = bucket.location.toLowerCase();
|
|
31
37
|
logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
|
|
32
38
|
}
|
|
@@ -9,7 +9,7 @@ async function ensureTriggerRegions(want) {
|
|
|
9
9
|
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
|
|
10
10
|
continue;
|
|
11
11
|
}
|
|
12
|
-
regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep
|
|
12
|
+
regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
|
|
13
13
|
}
|
|
14
14
|
await Promise.all(regionLookups);
|
|
15
15
|
}
|