firebase-tools 10.7.1 → 10.9.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/ext-configure.js +26 -15
- package/lib/commands/ext-export.js +14 -5
- package/lib/commands/ext-install.js +31 -2
- package/lib/commands/ext-update.js +17 -10
- package/lib/commands/functions-list.js +12 -20
- package/lib/commands/functions-secrets-set.js +1 -13
- package/lib/deploy/extensions/planner.js +12 -0
- package/lib/deploy/extensions/tasks.js +13 -0
- package/lib/deploy/functions/backend.js +47 -14
- package/lib/deploy/functions/build.js +28 -9
- package/lib/deploy/functions/checkIam.js +65 -53
- package/lib/deploy/functions/containerCleaner.js +8 -7
- package/lib/deploy/functions/functionsDeployHelper.js +1 -1
- package/lib/deploy/functions/prepare.js +42 -15
- package/lib/deploy/functions/pricing.js +2 -2
- package/lib/deploy/functions/release/executor.js +1 -1
- package/lib/deploy/functions/release/fabricator.js +66 -11
- package/lib/deploy/functions/release/index.js +0 -21
- package/lib/deploy/functions/runtimes/discovery/index.js +2 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +13 -1
- package/lib/deploy/functions/runtimes/golang/index.js +3 -0
- package/lib/deploy/functions/runtimes/node/index.js +23 -20
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +108 -1
- package/lib/deploy/functions/services/storage.js +6 -12
- package/lib/deploy/functions/validate.js +80 -16
- package/lib/deploy/index.js +2 -1
- package/lib/emulator/auth/cloudFunctions.js +6 -2
- package/lib/emulator/auth/operations.js +0 -1
- package/lib/emulator/auth/server.js +8 -1
- package/lib/emulator/auth/state.js +27 -24
- package/lib/emulator/controller.js +10 -5
- package/lib/emulator/databaseEmulator.js +36 -3
- package/lib/emulator/downloadableEmulators.js +18 -34
- package/lib/emulator/extensionsEmulator.js +4 -1
- package/lib/emulator/functionsEmulator.js +6 -7
- package/lib/emulator/functionsEmulatorRuntime.js +1 -1
- package/lib/emulator/functionsEmulatorShared.js +3 -0
- package/lib/emulator/functionsEmulatorUtils.js +5 -1
- package/lib/emulator/storage/apis/firebase.js +26 -4
- package/lib/extensions/askUserForEventsConfig.js +97 -0
- package/lib/extensions/export.js +7 -0
- package/lib/extensions/extensionsApi.js +47 -7
- package/lib/extensions/manifest.js +1 -1
- package/lib/extensions/paramHelper.js +2 -0
- package/lib/extensions/updateHelper.js +7 -1
- package/lib/extensions/warnings.js +11 -4
- package/lib/frameworks/index.js +111 -0
- package/lib/functions/functionslog.js +4 -9
- package/lib/gcp/cloudfunctions.js +1 -1
- package/lib/gcp/cloudfunctionsv2.js +14 -9
- package/lib/gcp/serviceusage.js +24 -0
- package/lib/hosting/normalizedHostingConfigs.js +3 -0
- package/lib/previews.js +1 -1
- package/lib/serve/index.js +2 -1
- package/lib/throttler/throttler.js +2 -1
- package/npm-shrinkwrap.json +315 -534
- package/package.json +4 -4
- package/templates/extensions/javascript/package.lint.json +5 -5
- package/templates/extensions/javascript/package.nolint.json +3 -3
- package/templates/extensions/typescript/package.lint.json +8 -7
- package/templates/extensions/typescript/package.nolint.json +2 -1
- package/templates/init/functions/typescript/package.lint.json +1 -0
- package/templates/init/functions/typescript/package.nolint.json +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.
|
|
3
|
+
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.obtainDefaultComputeServiceAgentBindings = exports.obtainPubSubServiceAgentBindings = exports.checkHttpIam = exports.checkServiceAccountIam = exports.EVENTARC_EVENT_RECEIVER_ROLE = exports.RUN_INVOKER_ROLE = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
|
|
4
4
|
const cli_color_1 = require("cli-color");
|
|
5
5
|
const logger_1 = require("../../logger");
|
|
6
6
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
@@ -16,7 +16,6 @@ const PERMISSION = "cloudfunctions.functions.setIamPolicy";
|
|
|
16
16
|
exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
|
|
17
17
|
exports.RUN_INVOKER_ROLE = "roles/run.invoker";
|
|
18
18
|
exports.EVENTARC_EVENT_RECEIVER_ROLE = "roles/eventarc.eventReceiver";
|
|
19
|
-
exports.EVENTARC_SERVICE_AGENT_ROLE = "roles/eventarc.serviceAgent";
|
|
20
19
|
async function checkServiceAccountIam(projectId) {
|
|
21
20
|
const saEmail = `${projectId}@appspot.gserviceaccount.com`;
|
|
22
21
|
let passed = false;
|
|
@@ -68,6 +67,12 @@ async function checkHttpIam(context, options, payload) {
|
|
|
68
67
|
logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
|
|
69
68
|
}
|
|
70
69
|
exports.checkHttpIam = checkHttpIam;
|
|
70
|
+
function getPubsubServiceAgent(projectNumber) {
|
|
71
|
+
return `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
72
|
+
}
|
|
73
|
+
function getDefaultComputeServiceAgent(projectNumber) {
|
|
74
|
+
return `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;
|
|
75
|
+
}
|
|
71
76
|
function reduceEventsToServices(services, endpoint) {
|
|
72
77
|
const service = (0, services_1.serviceForEndpoint)(endpoint);
|
|
73
78
|
if (service.requiredProjectBindings && !services.find((s) => s.name === service.name)) {
|
|
@@ -75,90 +80,97 @@ function reduceEventsToServices(services, endpoint) {
|
|
|
75
80
|
}
|
|
76
81
|
return services;
|
|
77
82
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (!binding.members.find((m) => m === serviceAccount)) {
|
|
87
|
-
binding.members.push(serviceAccount);
|
|
88
|
-
}
|
|
89
|
-
return binding;
|
|
90
|
-
}
|
|
91
|
-
exports.obtainBinding = obtainBinding;
|
|
92
|
-
function obtainPubSubServiceAgentBindings(projectNumber, existingPolicy) {
|
|
93
|
-
const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
94
|
-
return [obtainBinding(existingPolicy, pubsubServiceAgent, exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)];
|
|
83
|
+
function obtainPubSubServiceAgentBindings(projectNumber) {
|
|
84
|
+
const serviceAccountTokenCreatorBinding = {
|
|
85
|
+
role: exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
|
|
86
|
+
members: [getPubsubServiceAgent(projectNumber)],
|
|
87
|
+
};
|
|
88
|
+
return [serviceAccountTokenCreatorBinding];
|
|
95
89
|
}
|
|
96
90
|
exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
|
|
97
|
-
function obtainDefaultComputeServiceAgentBindings(projectNumber
|
|
98
|
-
const defaultComputeServiceAgent =
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
function obtainDefaultComputeServiceAgentBindings(projectNumber) {
|
|
92
|
+
const defaultComputeServiceAgent = getDefaultComputeServiceAgent(projectNumber);
|
|
93
|
+
const runInvokerBinding = {
|
|
94
|
+
role: exports.RUN_INVOKER_ROLE,
|
|
95
|
+
members: [defaultComputeServiceAgent],
|
|
96
|
+
};
|
|
97
|
+
const eventarcEventReceiverBinding = {
|
|
98
|
+
role: exports.EVENTARC_EVENT_RECEIVER_ROLE,
|
|
99
|
+
members: [defaultComputeServiceAgent],
|
|
100
|
+
};
|
|
101
|
+
return [runInvokerBinding, eventarcEventReceiverBinding];
|
|
102
102
|
}
|
|
103
103
|
exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (requiredBindings.length === 0) {
|
|
104
|
+
function mergeBindings(policy, requiredBindings) {
|
|
105
|
+
let updated = false;
|
|
106
|
+
for (const requiredBinding of requiredBindings) {
|
|
107
|
+
const match = policy.bindings.find((b) => b.role === requiredBinding.role);
|
|
108
|
+
if (!match) {
|
|
109
|
+
updated = true;
|
|
110
|
+
policy.bindings.push(requiredBinding);
|
|
112
111
|
continue;
|
|
113
112
|
}
|
|
114
|
-
for (const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
continue;
|
|
113
|
+
for (const requiredMember of requiredBinding.members) {
|
|
114
|
+
if (!match.members.find((m) => m === requiredMember)) {
|
|
115
|
+
updated = true;
|
|
116
|
+
match.members.push(requiredMember);
|
|
119
117
|
}
|
|
120
|
-
requiredBinding.members.forEach((updatedMember) => {
|
|
121
|
-
if (!policy.bindings[ndx].members.find((member) => member === updatedMember)) {
|
|
122
|
-
policy.bindings[ndx].members.push(updatedMember);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
118
|
}
|
|
126
119
|
}
|
|
120
|
+
return updated;
|
|
127
121
|
}
|
|
128
122
|
exports.mergeBindings = mergeBindings;
|
|
129
|
-
|
|
123
|
+
function printManualIamConfig(requiredBindings, projectId) {
|
|
124
|
+
utils.logLabeledBullet("functions", "Failed to verify the project has the correct IAM bindings for a successful deployment.", "warn");
|
|
125
|
+
utils.logLabeledBullet("functions", "You can either re-run `firebase deploy` as a project owner or manually run the following set of `gcloud` commands:", "warn");
|
|
126
|
+
for (const binding of requiredBindings) {
|
|
127
|
+
for (const member of binding.members) {
|
|
128
|
+
utils.logLabeledBullet("functions", `\`gcloud projects add-iam-policy-binding ${projectId} ` +
|
|
129
|
+
`--member=${member} ` +
|
|
130
|
+
`--role=${binding.role}\``, "warn");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
|
|
130
135
|
const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
|
|
131
136
|
const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
|
|
132
137
|
const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
|
|
133
138
|
if (newServices.length === 0) {
|
|
134
139
|
return;
|
|
135
140
|
}
|
|
141
|
+
const requiredBindingsPromises = [];
|
|
142
|
+
for (const service of newServices) {
|
|
143
|
+
requiredBindingsPromises.push(service.requiredProjectBindings(projectNumber));
|
|
144
|
+
}
|
|
145
|
+
const nestedRequiredBindings = await Promise.all(requiredBindingsPromises);
|
|
146
|
+
const requiredBindings = [...(0, functional_1.flattenArray)(nestedRequiredBindings)];
|
|
147
|
+
if (haveServices.length === 0) {
|
|
148
|
+
requiredBindings.push(...obtainPubSubServiceAgentBindings(projectNumber));
|
|
149
|
+
requiredBindings.push(...obtainDefaultComputeServiceAgentBindings(projectNumber));
|
|
150
|
+
}
|
|
151
|
+
if (requiredBindings.length === 0) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
136
154
|
let policy;
|
|
137
155
|
try {
|
|
138
156
|
policy = await (0, resourceManager_1.getIamPolicy)(projectNumber);
|
|
139
157
|
}
|
|
140
158
|
catch (err) {
|
|
159
|
+
printManualIamConfig(requiredBindings, projectId);
|
|
141
160
|
utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
|
|
142
161
|
`${newServices.map((service) => service.api).join(", ")}` +
|
|
143
162
|
". Deployment may fail.", "warn");
|
|
144
163
|
return;
|
|
145
164
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const allRequiredBindings = await Promise.all(findRequiredBindings);
|
|
149
|
-
if (haveServices.length === 0) {
|
|
150
|
-
allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy));
|
|
151
|
-
allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy));
|
|
152
|
-
allRequiredBindings.push(obtainEventarcServiceAgentBindings(projectNumber, policy));
|
|
153
|
-
}
|
|
154
|
-
if (!allRequiredBindings.find((bindings) => bindings.length > 0)) {
|
|
165
|
+
const hasUpdatedBindings = mergeBindings(policy, requiredBindings);
|
|
166
|
+
if (!hasUpdatedBindings) {
|
|
155
167
|
return;
|
|
156
168
|
}
|
|
157
|
-
mergeBindings(policy, allRequiredBindings);
|
|
158
169
|
try {
|
|
159
170
|
await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
|
|
160
171
|
}
|
|
161
172
|
catch (err) {
|
|
173
|
+
printManualIamConfig(requiredBindings, projectId);
|
|
162
174
|
throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
|
|
163
175
|
"deployment requires specific roles to be granted to service agents," +
|
|
164
176
|
" otherwise the deployment will fail.", { original: err });
|
|
@@ -251,13 +251,14 @@ class DockerHelper {
|
|
|
251
251
|
this.client = new docker.Client(origin);
|
|
252
252
|
}
|
|
253
253
|
async ls(path) {
|
|
254
|
-
if (!this.cache
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
if (!(path in this.cache)) {
|
|
255
|
+
this.cache[path] = retry(() => this.client.listTags(path)).then((res) => {
|
|
256
|
+
return {
|
|
257
|
+
tags: res.tags,
|
|
258
|
+
digests: Object.keys(res.manifest),
|
|
259
|
+
children: res.child,
|
|
260
|
+
};
|
|
261
|
+
});
|
|
261
262
|
}
|
|
262
263
|
return this.cache[path];
|
|
263
264
|
}
|
|
@@ -70,7 +70,7 @@ exports.getEndpointFilters = getEndpointFilters;
|
|
|
70
70
|
function getFunctionLabel(fn) {
|
|
71
71
|
let id = `${fn.id}(${fn.region})`;
|
|
72
72
|
if (fn.codebase && fn.codebase !== projectConfig_1.DEFAULT_CODEBASE) {
|
|
73
|
-
id =
|
|
73
|
+
id = `${fn.codebase}:${id}`;
|
|
74
74
|
}
|
|
75
75
|
return id;
|
|
76
76
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.inferBlockingDetails = exports.inferDetailsFromExisting = exports.prepare = void 0;
|
|
3
|
+
exports.resolveCpu = exports.inferBlockingDetails = exports.inferDetailsFromExisting = exports.prepare = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const backend = require("./backend");
|
|
6
|
+
const build = require("./build");
|
|
6
7
|
const ensureApiEnabled = require("../../ensureApiEnabled");
|
|
7
8
|
const functionsConfig = require("../../functionsConfig");
|
|
8
9
|
const functionsEnv = require("../../functions/env");
|
|
@@ -22,6 +23,7 @@ const error_1 = require("../../error");
|
|
|
22
23
|
const projectConfig_1 = require("../../functions/projectConfig");
|
|
23
24
|
const previews_1 = require("../../previews");
|
|
24
25
|
const v1_1 = require("../../functions/events/v1");
|
|
26
|
+
const serviceusage_1 = require("../../gcp/serviceusage");
|
|
25
27
|
function hasUserConfig(config) {
|
|
26
28
|
return Object.keys(config).length > 1;
|
|
27
29
|
}
|
|
@@ -76,9 +78,17 @@ async function prepare(context, options, payload) {
|
|
|
76
78
|
projectAlias: options.projectAlias,
|
|
77
79
|
};
|
|
78
80
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
82
|
+
let wantBackend;
|
|
83
|
+
if (previews_1.previews.functionsparams) {
|
|
84
|
+
const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
|
|
85
|
+
wantBackend = build.resolveBackend(wantBuild, userEnvs);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
89
|
+
wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
|
|
90
|
+
}
|
|
91
|
+
wantBackend.environmentVariables = envs;
|
|
82
92
|
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
83
93
|
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
84
94
|
endpoint.codebase = codebase;
|
|
@@ -98,15 +108,6 @@ async function prepare(context, options, payload) {
|
|
|
98
108
|
(0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
|
|
99
109
|
}
|
|
100
110
|
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
101
|
-
if (!previews_1.previews.functionsv2) {
|
|
102
|
-
throw new error_1.FirebaseError("This version of firebase-tools does not support Google Cloud " +
|
|
103
|
-
"Functions gen 2\n" +
|
|
104
|
-
"If Cloud Functions for Firebase gen 2 is still in alpha, sign up " +
|
|
105
|
-
"for the alpha program at " +
|
|
106
|
-
"https://services.google.com/fb/forms/firebasealphaprogram/\n" +
|
|
107
|
-
"If Cloud Functions for Firebase gen 2 is in beta, get the latest " +
|
|
108
|
-
"version of Firebse Tools with `npm i -g firebase-tools@latest`");
|
|
109
|
-
}
|
|
110
111
|
source.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, config);
|
|
111
112
|
}
|
|
112
113
|
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
@@ -117,12 +118,13 @@ async function prepare(context, options, payload) {
|
|
|
117
118
|
payload.functions = {};
|
|
118
119
|
const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(await backend.existingBackend(context)));
|
|
119
120
|
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
120
|
-
const haveBackend = haveBackends[codebase] ||
|
|
121
|
+
const haveBackend = haveBackends[codebase] || backend.empty();
|
|
121
122
|
payload.functions[codebase] = { wantBackend, haveBackend };
|
|
122
123
|
}
|
|
123
124
|
for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
|
|
124
125
|
inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
|
|
125
126
|
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
127
|
+
resolveCpu(wantBackend);
|
|
126
128
|
validate.endpointsAreValid(wantBackend);
|
|
127
129
|
inferBlockingDetails(wantBackend);
|
|
128
130
|
}
|
|
@@ -153,6 +155,11 @@ async function prepare(context, options, payload) {
|
|
|
153
155
|
return ensureApiEnabled.ensure(context.projectId, api, "functions");
|
|
154
156
|
});
|
|
155
157
|
await Promise.all(enablements);
|
|
158
|
+
const services = ["pubsub.googleapis.com", "eventarc.googleapis.com"];
|
|
159
|
+
const generateServiceAccounts = services.map((service) => {
|
|
160
|
+
return (0, serviceusage_1.generateServiceIdentity)(projectNumber, service, "functions");
|
|
161
|
+
});
|
|
162
|
+
await Promise.all(generateServiceAccounts);
|
|
156
163
|
}
|
|
157
164
|
const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
|
|
158
165
|
return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, context.filters);
|
|
@@ -160,7 +167,7 @@ async function prepare(context, options, payload) {
|
|
|
160
167
|
await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
|
|
161
168
|
await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
|
|
162
169
|
await backend.checkAvailability(context, matchingBackend);
|
|
163
|
-
await (0, checkIam_1.ensureServiceAgentRoles)(projectNumber, matchingBackend, haveBackend);
|
|
170
|
+
await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend);
|
|
164
171
|
await validate.secretsAreValid(projectId, matchingBackend);
|
|
165
172
|
await ensure.secretAccess(projectId, matchingBackend, haveBackend);
|
|
166
173
|
}
|
|
@@ -178,6 +185,12 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
|
178
185
|
if (!wantE.availableMemoryMb && haveE.availableMemoryMb) {
|
|
179
186
|
wantE.availableMemoryMb = haveE.availableMemoryMb;
|
|
180
187
|
}
|
|
188
|
+
if (!wantE.concurrency && haveE.concurrency) {
|
|
189
|
+
wantE.concurrency = haveE.concurrency;
|
|
190
|
+
}
|
|
191
|
+
if (!wantE.cpu && haveE.cpu) {
|
|
192
|
+
wantE.cpu = haveE.cpu;
|
|
193
|
+
}
|
|
181
194
|
wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
|
|
182
195
|
maybeCopyTriggerRegion(wantE, haveE);
|
|
183
196
|
}
|
|
@@ -223,3 +236,17 @@ function inferBlockingDetails(want) {
|
|
|
223
236
|
}
|
|
224
237
|
}
|
|
225
238
|
exports.inferBlockingDetails = inferBlockingDetails;
|
|
239
|
+
function resolveCpu(want) {
|
|
240
|
+
for (const e of backend.allEndpoints(want)) {
|
|
241
|
+
if (e.platform === "gcfv1") {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (e.cpu === "gcf_gen1") {
|
|
245
|
+
e.cpu = backend.memoryToGen1Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY);
|
|
246
|
+
}
|
|
247
|
+
else if (!e.cpu) {
|
|
248
|
+
e.cpu = backend.memoryToGen2Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
exports.resolveCpu = resolveCpu;
|
|
@@ -145,12 +145,12 @@ function monthlyMinInstanceCost(endpoints) {
|
|
|
145
145
|
usage["gcfv1"][tier].cpu + cpu * SECONDS_PER_MONTH * endpoint.minInstances;
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
|
-
const cpu = 1;
|
|
149
148
|
const tier = V2_REGION_TO_TIER[endpoint.region];
|
|
150
149
|
usage["gcfv2"][tier].ram =
|
|
151
150
|
usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
|
|
152
151
|
usage["gcfv2"][tier].cpu =
|
|
153
|
-
usage["gcfv2"][tier].cpu +
|
|
152
|
+
usage["gcfv2"][tier].cpu +
|
|
153
|
+
endpoint.cpu * SECONDS_PER_MONTH * endpoint.minInstances;
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
let v1MemoryBill = usage["gcfv1"][1].ram * exports.V1_RATES.memoryGb[1] + usage["gcfv1"][2].ram * exports.V1_RATES.memoryGb[2];
|
|
@@ -13,7 +13,7 @@ async function handler(op) {
|
|
|
13
13
|
((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
|
|
14
14
|
((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
|
|
15
15
|
((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode);
|
|
16
|
-
if (code === 429 || code === 409) {
|
|
16
|
+
if (code === 429 || code === 409 || code === 503) {
|
|
17
17
|
throw err;
|
|
18
18
|
}
|
|
19
19
|
op.error = err;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Fabricator = void 0;
|
|
3
|
+
exports.serviceIsResolved = exports.Fabricator = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const error_1 = require("../../../error");
|
|
6
6
|
const sourceTokenScraper_1 = require("./sourceTokenScraper");
|
|
@@ -23,6 +23,7 @@ const scheduler = require("../../../gcp/cloudscheduler");
|
|
|
23
23
|
const utils = require("../../../utils");
|
|
24
24
|
const services = require("../services");
|
|
25
25
|
const v1_1 = require("../../../functions/events/v1");
|
|
26
|
+
const throttler_1 = require("../../../throttler/throttler");
|
|
26
27
|
const gcfV1PollerOptions = {
|
|
27
28
|
apiOrigin: api_1.functionsOrigin,
|
|
28
29
|
apiVersion: gcf.API_VERSION,
|
|
@@ -271,8 +272,16 @@ class Fabricator {
|
|
|
271
272
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
272
273
|
}
|
|
273
274
|
const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
const hasCustomCPU = endpoint.cpu !== backend.memoryToGen1Cpu(mem);
|
|
276
|
+
if (!endpoint.concurrency) {
|
|
277
|
+
endpoint.concurrency =
|
|
278
|
+
endpoint.cpu >= backend.MIN_CPU_FOR_CONCURRENCY
|
|
279
|
+
? backend.DEFAULT_CONCURRENCY
|
|
280
|
+
: 1;
|
|
281
|
+
}
|
|
282
|
+
const hasConcurrency = endpoint.concurrency !== 1;
|
|
283
|
+
if (hasCustomCPU || hasConcurrency) {
|
|
284
|
+
await this.setRunTraits(serviceName, endpoint);
|
|
276
285
|
}
|
|
277
286
|
}
|
|
278
287
|
async updateV1Function(endpoint, scraper) {
|
|
@@ -343,8 +352,17 @@ class Fabricator {
|
|
|
343
352
|
.run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker))
|
|
344
353
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
345
354
|
}
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
const hasCustomCPU = endpoint.cpu !==
|
|
356
|
+
backend.memoryToGen1Cpu(endpoint.availableMemoryMb || backend.DEFAULT_MEMORY);
|
|
357
|
+
const explicitConcurrency = endpoint.concurrency !== undefined;
|
|
358
|
+
if (hasCustomCPU || explicitConcurrency) {
|
|
359
|
+
if (endpoint.concurrency === undefined) {
|
|
360
|
+
endpoint.concurrency =
|
|
361
|
+
endpoint.cpu < backend.MIN_CPU_FOR_CONCURRENCY
|
|
362
|
+
? 1
|
|
363
|
+
: backend.DEFAULT_CONCURRENCY;
|
|
364
|
+
}
|
|
365
|
+
await this.setRunTraits(serviceName, endpoint);
|
|
348
366
|
}
|
|
349
367
|
}
|
|
350
368
|
async deleteV1Function(endpoint) {
|
|
@@ -367,18 +385,32 @@ class Fabricator {
|
|
|
367
385
|
})
|
|
368
386
|
.catch(rethrowAs(endpoint, "delete"));
|
|
369
387
|
}
|
|
370
|
-
async
|
|
388
|
+
async setRunTraits(serviceName, endpoint) {
|
|
371
389
|
await this.functionExecutor
|
|
372
390
|
.run(async () => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
let service = await run.getService(serviceName);
|
|
392
|
+
let changed = false;
|
|
393
|
+
if (service.spec.template.spec.containerConcurrency !== endpoint.concurrency) {
|
|
394
|
+
service.spec.template.spec.containerConcurrency = endpoint.concurrency;
|
|
395
|
+
changed = true;
|
|
396
|
+
}
|
|
397
|
+
if (+service.spec.template.spec.containers[0].resources.limits.cpu !== endpoint.cpu) {
|
|
398
|
+
service.spec.template.spec.containers[0].resources.limits.cpu = `${endpoint.cpu}`;
|
|
399
|
+
changed = true;
|
|
400
|
+
}
|
|
401
|
+
if (!changed) {
|
|
402
|
+
logger_1.logger.debug("Skipping setRunTraits on", serviceName, " because it already matches");
|
|
376
403
|
return;
|
|
377
404
|
}
|
|
378
405
|
delete service.status;
|
|
379
406
|
delete service.spec.template.metadata.name;
|
|
380
|
-
service
|
|
381
|
-
|
|
407
|
+
service = await run.replaceService(serviceName, service);
|
|
408
|
+
let retry = 0;
|
|
409
|
+
while (!exports.serviceIsResolved(service)) {
|
|
410
|
+
await (0, throttler_1.backoff)(retry, 2, 30);
|
|
411
|
+
retry = retry + 1;
|
|
412
|
+
service = await run.getService(serviceName);
|
|
413
|
+
}
|
|
382
414
|
})
|
|
383
415
|
.catch(rethrowAs(endpoint, "set concurrency"));
|
|
384
416
|
}
|
|
@@ -482,3 +514,26 @@ class Fabricator {
|
|
|
482
514
|
}
|
|
483
515
|
}
|
|
484
516
|
exports.Fabricator = Fabricator;
|
|
517
|
+
function serviceIsResolved(service) {
|
|
518
|
+
var _a, _b, _c, _d, _e;
|
|
519
|
+
if (((_a = service.status) === null || _a === void 0 ? void 0 : _a.observedGeneration) !== service.metadata.generation) {
|
|
520
|
+
logger_1.logger.debug(`Service ${service.metadata.name} is not resolved because` +
|
|
521
|
+
`observed generation ${(_b = service.status) === null || _b === void 0 ? void 0 : _b.observedGeneration} does not ` +
|
|
522
|
+
`match spec generation ${service.metadata.generation}`);
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const readyCondition = (_d = (_c = service.status) === null || _c === void 0 ? void 0 : _c.conditions) === null || _d === void 0 ? void 0 : _d.find((condition) => {
|
|
526
|
+
return condition.type === "Ready";
|
|
527
|
+
});
|
|
528
|
+
if ((readyCondition === null || readyCondition === void 0 ? void 0 : readyCondition.status) === "Unknown") {
|
|
529
|
+
logger_1.logger.debug(`Waiting for service ${service.metadata.name} to be ready. ` +
|
|
530
|
+
`Status is ${JSON.stringify((_e = service.status) === null || _e === void 0 ? void 0 : _e.conditions)}`);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
else if ((readyCondition === null || readyCondition === void 0 ? void 0 : readyCondition.status) === "True") {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
logger_1.logger.debug(`Service ${service.metadata.name} has unexpected ready status ${JSON.stringify(readyCondition)}. It may have failed rollout.`);
|
|
537
|
+
throw new error_1.FirebaseError(`Unexpected Status ${readyCondition === null || readyCondition === void 0 ? void 0 : readyCondition.status} for service ${service.metadata.name}`);
|
|
538
|
+
}
|
|
539
|
+
exports.serviceIsResolved = serviceIsResolved;
|
|
@@ -11,12 +11,9 @@ const fabricator = require("./fabricator");
|
|
|
11
11
|
const reporter = require("./reporter");
|
|
12
12
|
const executor = require("./executor");
|
|
13
13
|
const prompts = require("../prompts");
|
|
14
|
-
const secrets = require("../../../functions/secrets");
|
|
15
14
|
const functionsConfig_1 = require("../../../functionsConfig");
|
|
16
15
|
const functionsDeployHelper_1 = require("../functionsDeployHelper");
|
|
17
16
|
const error_1 = require("../../../error");
|
|
18
|
-
const projectUtils_1 = require("../../../projectUtils");
|
|
19
|
-
const utils_1 = require("../../../utils");
|
|
20
17
|
async function release(context, options, payload) {
|
|
21
18
|
if (!context.config) {
|
|
22
19
|
return;
|
|
@@ -76,24 +73,6 @@ async function release(context, options, payload) {
|
|
|
76
73
|
const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
|
|
77
74
|
throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
|
|
78
75
|
}
|
|
79
|
-
else {
|
|
80
|
-
if (secrets.of(haveEndpoints).length > 0) {
|
|
81
|
-
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
82
|
-
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
83
|
-
const reloadedBackend = await backend.existingBackend({ projectId });
|
|
84
|
-
const prunedResult = await secrets.pruneAndDestroySecrets({ projectId, projectNumber }, backend.allEndpoints(reloadedBackend));
|
|
85
|
-
if (prunedResult.destroyed.length > 0) {
|
|
86
|
-
(0, utils_1.logLabeledBullet)("functions", `Destroyed unused secret versions: ${prunedResult.destroyed
|
|
87
|
-
.map((s) => `${s.secret}@${s.version}`)
|
|
88
|
-
.join(", ")}`);
|
|
89
|
-
}
|
|
90
|
-
if (prunedResult.erred.length > 0) {
|
|
91
|
-
(0, utils_1.logLabeledWarning)("functions", `Failed to destroy unused secret versions:\n\t${prunedResult.erred
|
|
92
|
-
.map((err) => err.message)
|
|
93
|
-
.join("\n\t")}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
76
|
}
|
|
98
77
|
exports.release = release;
|
|
99
78
|
function printTriggerUrls(results) {
|
|
@@ -71,7 +71,8 @@ async function detectFromPort(port, project, runtime, timeout = 30000) {
|
|
|
71
71
|
parsed = yaml.load(text);
|
|
72
72
|
}
|
|
73
73
|
catch (err) {
|
|
74
|
-
|
|
74
|
+
logger_1.logger.debug("Failed to parse functions.yaml", err);
|
|
75
|
+
throw new error_1.FirebaseError(`Failed to load function definition from source: ${text}`);
|
|
75
76
|
}
|
|
76
77
|
return yamlToBackend(parsed, project, api.functionsDefaultRegion, runtime);
|
|
77
78
|
}
|
|
@@ -187,7 +187,19 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
|
|
|
187
187
|
region,
|
|
188
188
|
project,
|
|
189
189
|
runtime, entryPoint: ep.entryPoint }, triggered);
|
|
190
|
-
(0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
|
|
190
|
+
(0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "cpu");
|
|
191
|
+
(0, proto_1.renameIfPresent)(parsed, ep, "secretEnvironmentVariables", "secretEnvironmentVariables", (senvs) => {
|
|
192
|
+
if (senvs && senvs.length > 0) {
|
|
193
|
+
ep.secretEnvironmentVariables = [];
|
|
194
|
+
for (const { key, secret } of senvs) {
|
|
195
|
+
ep.secretEnvironmentVariables.push({
|
|
196
|
+
key,
|
|
197
|
+
secret: secret || key,
|
|
198
|
+
projectId: project,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
191
203
|
allParsed.push(parsed);
|
|
192
204
|
}
|
|
193
205
|
return allParsed;
|
|
@@ -105,6 +105,9 @@ class Delegate {
|
|
|
105
105
|
return p;
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
async discoverBuild(configValues, envs) {
|
|
109
|
+
return { requiredAPIs: [], endpoints: {}, params: [] };
|
|
110
|
+
}
|
|
108
111
|
async discoverSpec(configValues, envs) {
|
|
109
112
|
let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
|
|
110
113
|
if (!discovered) {
|
|
@@ -11,7 +11,6 @@ const node_fetch_1 = require("node-fetch");
|
|
|
11
11
|
const error_1 = require("../../../../error");
|
|
12
12
|
const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
|
|
13
13
|
const logger_1 = require("../../../../logger");
|
|
14
|
-
const previews_1 = require("../../../../previews");
|
|
15
14
|
const utils_1 = require("../../../../utils");
|
|
16
15
|
const discovery = require("../discovery");
|
|
17
16
|
const validate = require("./validate");
|
|
@@ -83,28 +82,32 @@ class Delegate {
|
|
|
83
82
|
});
|
|
84
83
|
}
|
|
85
84
|
async discoverSpec(config, env) {
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
if (!semver.valid(this.sdkVersion)) {
|
|
86
|
+
logger_1.logger.debug(`Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.`);
|
|
87
|
+
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
88
|
+
}
|
|
89
|
+
if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
|
|
90
|
+
(0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
|
|
91
|
+
`Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
|
|
92
|
+
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
93
|
+
}
|
|
94
|
+
let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
|
|
95
|
+
if (!discovered) {
|
|
96
|
+
const getPort = (0, util_1.promisify)(portfinder.getPort);
|
|
97
|
+
const port = await getPort();
|
|
98
|
+
const kill = await this.serve(port, env);
|
|
99
|
+
try {
|
|
100
|
+
discovered = await discovery.detectFromPort(port, this.projectId, this.runtime);
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
}
|
|
102
|
+
finally {
|
|
103
|
+
await kill();
|
|
103
104
|
}
|
|
104
|
-
discovered.environmentVariables = env;
|
|
105
|
-
return discovered;
|
|
106
105
|
}
|
|
107
|
-
|
|
106
|
+
discovered.environmentVariables = env;
|
|
107
|
+
return discovered;
|
|
108
|
+
}
|
|
109
|
+
async discoverBuild(config, env) {
|
|
110
|
+
return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
exports.Delegate = Delegate;
|