firebase-tools 9.23.0 → 9.23.1
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 +5 -1
- package/lib/deploy/functions/checkIam.js +65 -4
- package/lib/deploy/functions/containerCleaner.js +28 -38
- package/lib/deploy/functions/eventTypes.js +10 -0
- package/lib/deploy/functions/prepare.js +12 -2
- package/lib/deploy/functions/release/fabricator.js +3 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +3 -9
- package/lib/deploy/functions/services/index.js +38 -0
- package/lib/deploy/functions/services/storage.js +43 -0
- package/lib/deploy/functions/triggerRegionHelper.js +6 -30
- package/lib/emulator/auth/operations.js +27 -9
- package/lib/emulator/auth/widget_ui.js +14 -0
- package/lib/ensureApiEnabled.js +10 -12
- package/lib/gcp/cloudfunctions.js +11 -2
- package/lib/gcp/cloudfunctionsv2.js +35 -3
- package/lib/gcp/docker.js +29 -1
- package/lib/gcp/location.js +44 -0
- package/lib/gcp/storage.js +48 -32
- package/lib/init/features/project.js +2 -1
- package/lib/previews.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
-
|
|
1
|
+
- Corrects a bug where containers in Artifact Registry would not be deleted if a function has an upper case character in its name (#3918)
|
|
2
|
+
- Fixes issue where providing the `--project` flag during `init` would not be recognized with a default project already set. (#3870)
|
|
3
|
+
- Fixes issue with setting memory limits for some functions (#3924)
|
|
4
|
+
- New HTTPS functions only allow secure traffic. (#3923)
|
|
5
|
+
- No longer default-enable AR and don't send builds to AR unless an experiment is enabled (#3935)
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkHttpIam = exports.checkServiceAccountIam = void 0;
|
|
3
|
+
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.checkHttpIam = exports.checkServiceAccountIam = 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");
|
|
7
7
|
const error_1 = require("../../error");
|
|
8
|
-
const
|
|
8
|
+
const iam = require("../../gcp/iam");
|
|
9
9
|
const backend = require("./backend");
|
|
10
10
|
const track = require("../../track");
|
|
11
|
+
const utils = require("../../utils");
|
|
12
|
+
const resourceManager_1 = require("../../gcp/resourceManager");
|
|
13
|
+
const services_1 = require("./services");
|
|
11
14
|
const PERMISSION = "cloudfunctions.functions.setIamPolicy";
|
|
12
15
|
async function checkServiceAccountIam(projectId) {
|
|
13
16
|
const saEmail = `${projectId}@appspot.gserviceaccount.com`;
|
|
14
17
|
let passed = false;
|
|
15
18
|
try {
|
|
16
|
-
const iamResult = await
|
|
19
|
+
const iamResult = await iam.testResourceIamPermissions("https://iam.googleapis.com", "v1", `projects/${projectId}/serviceAccounts/${saEmail}`, ["iam.serviceAccounts.actAs"]);
|
|
17
20
|
passed = iamResult.passed;
|
|
18
21
|
}
|
|
19
22
|
catch (err) {
|
|
@@ -41,7 +44,7 @@ async function checkHttpIam(context, options, payload) {
|
|
|
41
44
|
logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
|
|
42
45
|
let passed = true;
|
|
43
46
|
try {
|
|
44
|
-
const iamResult = await
|
|
47
|
+
const iamResult = await iam.testIamPermissions(context.projectId, [PERMISSION]);
|
|
45
48
|
passed = iamResult.passed;
|
|
46
49
|
}
|
|
47
50
|
catch (e) {
|
|
@@ -57,3 +60,61 @@ async function checkHttpIam(context, options, payload) {
|
|
|
57
60
|
logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
|
|
58
61
|
}
|
|
59
62
|
exports.checkHttpIam = checkHttpIam;
|
|
63
|
+
function reduceEventsToServices(services, endpoint) {
|
|
64
|
+
const service = services_1.serviceForEndpoint(endpoint);
|
|
65
|
+
if (service.requiredProjectBindings && !services.find((s) => s.name === service.name)) {
|
|
66
|
+
services.push(service);
|
|
67
|
+
}
|
|
68
|
+
return services;
|
|
69
|
+
}
|
|
70
|
+
function mergeBindings(policy, allRequiredBindings) {
|
|
71
|
+
for (const requiredBindings of allRequiredBindings) {
|
|
72
|
+
if (requiredBindings.length === 0) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
for (const requiredBinding of requiredBindings) {
|
|
76
|
+
const ndx = policy.bindings.findIndex((policyBinding) => policyBinding.role === requiredBinding.role);
|
|
77
|
+
if (ndx === -1) {
|
|
78
|
+
policy.bindings.push(requiredBinding);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
requiredBinding.members.forEach((updatedMember) => {
|
|
82
|
+
if (!policy.bindings[ndx].members.find((member) => member === updatedMember)) {
|
|
83
|
+
policy.bindings[ndx].members.push(updatedMember);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.mergeBindings = mergeBindings;
|
|
90
|
+
async function ensureServiceAgentRoles(projectId, want, have) {
|
|
91
|
+
const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
|
|
92
|
+
const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
|
|
93
|
+
const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
|
|
94
|
+
if (newServices.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let policy;
|
|
98
|
+
try {
|
|
99
|
+
policy = await resourceManager_1.getIamPolicy(projectId);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
|
|
103
|
+
`${newServices.map((service) => service.api).join(", ")}` +
|
|
104
|
+
". Deployment may fail.", "warn");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const findRequiredBindings = [];
|
|
108
|
+
newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectId, policy)));
|
|
109
|
+
const allRequiredBindings = await Promise.all(findRequiredBindings);
|
|
110
|
+
mergeBindings(policy, allRequiredBindings);
|
|
111
|
+
try {
|
|
112
|
+
await resourceManager_1.setIamPolicy(projectId, policy, "bindings");
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
|
|
116
|
+
"deployment requires specific roles to be granted to service agents," +
|
|
117
|
+
" otherwise the deployment will fail.", { original: err });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.ensureServiceAgentRoles = ensureServiceAgentRoles;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.
|
|
3
|
+
exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.NoopArtifactRegistryCleaner = exports.ArtifactRegistryCleaner = exports.cleanupBuildImages = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const error_1 = require("../../error");
|
|
6
6
|
const api_1 = require("../../api");
|
|
@@ -10,31 +10,6 @@ const backend = require("./backend");
|
|
|
10
10
|
const docker = require("../../gcp/docker");
|
|
11
11
|
const utils = require("../../utils");
|
|
12
12
|
const poller = require("../../operation-poller");
|
|
13
|
-
exports.SUBDOMAIN_MAPPING = {
|
|
14
|
-
"us-west2": "us",
|
|
15
|
-
"us-west3": "us",
|
|
16
|
-
"us-west4": "us",
|
|
17
|
-
"us-central1": "us",
|
|
18
|
-
"us-central2": "us",
|
|
19
|
-
"us-east1": "us",
|
|
20
|
-
"us-east4": "us",
|
|
21
|
-
"northamerica-northeast1": "us",
|
|
22
|
-
"southamerica-east1": "us",
|
|
23
|
-
"europe-west1": "eu",
|
|
24
|
-
"europe-west2": "eu",
|
|
25
|
-
"europe-west3": "eu",
|
|
26
|
-
"europe-west5": "eu",
|
|
27
|
-
"europe-west6": "eu",
|
|
28
|
-
"europe-central2": "eu",
|
|
29
|
-
"asia-east1": "asia",
|
|
30
|
-
"asia-east2": "asia",
|
|
31
|
-
"asia-northeast1": "asia",
|
|
32
|
-
"asia-northeast2": "asia",
|
|
33
|
-
"asia-northeast3": "asia",
|
|
34
|
-
"asia-south1": "asia",
|
|
35
|
-
"asia-southeast2": "asia",
|
|
36
|
-
"australia-southeast1": "asia",
|
|
37
|
-
};
|
|
38
13
|
async function retry(func) {
|
|
39
14
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
40
15
|
const MAX_RETRIES = 3;
|
|
@@ -87,7 +62,7 @@ async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}
|
|
|
87
62
|
await gcrCleaner.cleanupFunction(func);
|
|
88
63
|
}
|
|
89
64
|
catch (err) {
|
|
90
|
-
const path = `${func.project}/${
|
|
65
|
+
const path = `${func.project}/${docker.GCR_SUBDOMAIN_MAPPING[func.region]}/gcf`;
|
|
91
66
|
failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
|
|
92
67
|
}
|
|
93
68
|
}));
|
|
@@ -108,7 +83,12 @@ async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}
|
|
|
108
83
|
exports.cleanupBuildImages = cleanupBuildImages;
|
|
109
84
|
class ArtifactRegistryCleaner {
|
|
110
85
|
static packagePath(func) {
|
|
111
|
-
|
|
86
|
+
const encodedId = func.id
|
|
87
|
+
.replace(/_/g, "__")
|
|
88
|
+
.replace(/-/g, "--")
|
|
89
|
+
.replace(/^[A-Z]/, (first) => `${first.toLowerCase()}-${first.toLowerCase()}`)
|
|
90
|
+
.replace(/[A-Z]/g, (upper) => `_${upper.toLowerCase()}`);
|
|
91
|
+
return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${encodedId}`;
|
|
112
92
|
}
|
|
113
93
|
async cleanupFunction(func) {
|
|
114
94
|
let op;
|
|
@@ -140,12 +120,21 @@ ArtifactRegistryCleaner.POLLER_OPTIONS = {
|
|
|
140
120
|
apiVersion: artifactregistry.API_VERSION,
|
|
141
121
|
masterTimeout: 5 * 60 * 1000,
|
|
142
122
|
};
|
|
123
|
+
class NoopArtifactRegistryCleaner extends ArtifactRegistryCleaner {
|
|
124
|
+
cleanupFunction() {
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
cleanupFunctionCache() {
|
|
128
|
+
return Promise.resolve();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.NoopArtifactRegistryCleaner = NoopArtifactRegistryCleaner;
|
|
143
132
|
class ContainerRegistryCleaner {
|
|
144
133
|
constructor() {
|
|
145
134
|
this.helpers = {};
|
|
146
135
|
}
|
|
147
136
|
helper(location) {
|
|
148
|
-
const subdomain =
|
|
137
|
+
const subdomain = docker.GCR_SUBDOMAIN_MAPPING[location] || "us";
|
|
149
138
|
if (!this.helpers[subdomain]) {
|
|
150
139
|
const origin = `https://${subdomain}.${api_1.containerRegistryDomain}`;
|
|
151
140
|
this.helpers[subdomain] = new DockerHelper(origin);
|
|
@@ -185,14 +174,14 @@ function getHelper(cache, subdomain) {
|
|
|
185
174
|
}
|
|
186
175
|
async function listGcfPaths(projectId, locations, dockerHelpers = {}) {
|
|
187
176
|
if (!locations) {
|
|
188
|
-
locations = Object.keys(
|
|
177
|
+
locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
|
|
189
178
|
}
|
|
190
|
-
const invalidRegion = locations.find((loc) => !
|
|
179
|
+
const invalidRegion = locations.find((loc) => !docker.GCR_SUBDOMAIN_MAPPING[loc]);
|
|
191
180
|
if (invalidRegion) {
|
|
192
181
|
throw new error_1.FirebaseError(`Invalid region ${invalidRegion} supplied`);
|
|
193
182
|
}
|
|
194
183
|
const locationsSet = new Set(locations);
|
|
195
|
-
const subdomains = new Set(Object.values(
|
|
184
|
+
const subdomains = new Set(Object.values(docker.GCR_SUBDOMAIN_MAPPING));
|
|
196
185
|
const failedSubdomains = [];
|
|
197
186
|
const listAll = [];
|
|
198
187
|
for (const subdomain of subdomains) {
|
|
@@ -223,26 +212,27 @@ async function listGcfPaths(projectId, locations, dockerHelpers = {}) {
|
|
|
223
212
|
throw new error_1.FirebaseError(`Failed to search the following subdomains: ${failedSubdomains.join(",")}`);
|
|
224
213
|
}
|
|
225
214
|
return gcfDirs.map((loc) => {
|
|
226
|
-
return `${
|
|
215
|
+
return `${docker.GCR_SUBDOMAIN_MAPPING[loc]}.${api_1.containerRegistryDomain}/${projectId}/gcf/${loc}`;
|
|
227
216
|
});
|
|
228
217
|
}
|
|
229
218
|
exports.listGcfPaths = listGcfPaths;
|
|
230
219
|
async function deleteGcfArtifacts(projectId, locations, dockerHelpers = {}) {
|
|
231
220
|
if (!locations) {
|
|
232
|
-
locations = Object.keys(
|
|
221
|
+
locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
|
|
233
222
|
}
|
|
234
|
-
const invalidRegion = locations.find((loc) => !
|
|
223
|
+
const invalidRegion = locations.find((loc) => !docker.GCR_SUBDOMAIN_MAPPING[loc]);
|
|
235
224
|
if (invalidRegion) {
|
|
236
225
|
throw new error_1.FirebaseError(`Invalid region ${invalidRegion} supplied`);
|
|
237
226
|
}
|
|
238
|
-
const subdomains = new Set(Object.values(
|
|
227
|
+
const subdomains = new Set(Object.values(docker.GCR_SUBDOMAIN_MAPPING));
|
|
239
228
|
const failedSubdomains = [];
|
|
240
229
|
const deleteLocations = locations.map((loc) => {
|
|
230
|
+
const subdomain = docker.GCR_SUBDOMAIN_MAPPING[loc];
|
|
241
231
|
try {
|
|
242
|
-
return getHelper(dockerHelpers,
|
|
232
|
+
return getHelper(dockerHelpers, subdomain).rm(`${projectId}/gcf/${loc}`);
|
|
243
233
|
}
|
|
244
234
|
catch (err) {
|
|
245
|
-
failedSubdomains.push(
|
|
235
|
+
failedSubdomains.push(subdomain);
|
|
246
236
|
logger_1.logger.debug(err);
|
|
247
237
|
}
|
|
248
238
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PUBSUB_V2_EVENT = exports.STORAGE_V2_EVENTS = void 0;
|
|
4
|
+
exports.STORAGE_V2_EVENTS = [
|
|
5
|
+
"google.cloud.storage.object.v1.finalized",
|
|
6
|
+
"google.cloud.storage.object.v1.archived",
|
|
7
|
+
"google.cloud.storage.object.v1.deleted",
|
|
8
|
+
"google.cloud.storage.object.v1.metadataUpdated",
|
|
9
|
+
];
|
|
10
|
+
exports.PUBSUB_V2_EVENT = "google.cloud.pubsub.topic.v1.messagePublished";
|
|
@@ -19,12 +19,20 @@ const validate = require("./validate");
|
|
|
19
19
|
const utils = require("../../utils");
|
|
20
20
|
const logger_1 = require("../../logger");
|
|
21
21
|
const triggerRegionHelper_1 = require("./triggerRegionHelper");
|
|
22
|
+
const checkIam_1 = require("./checkIam");
|
|
22
23
|
function hasUserConfig(config) {
|
|
23
24
|
return Object.keys(config).length > 1;
|
|
24
25
|
}
|
|
25
26
|
function hasDotenv(opts) {
|
|
26
27
|
return previews_1.previews.dotenv && functionsEnv.hasUserEnvs(opts);
|
|
27
28
|
}
|
|
29
|
+
async function maybeEnableAR(projectId) {
|
|
30
|
+
if (previews_1.previews.artifactregistry) {
|
|
31
|
+
return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
|
|
32
|
+
}
|
|
33
|
+
await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
28
36
|
async function prepare(context, options, payload) {
|
|
29
37
|
if (!options.config.src.functions) {
|
|
30
38
|
return;
|
|
@@ -39,9 +47,10 @@ async function prepare(context, options, payload) {
|
|
|
39
47
|
ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
|
|
40
48
|
ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
|
|
41
49
|
ensureCloudBuildEnabled_1.ensureCloudBuildEnabled(projectId),
|
|
42
|
-
|
|
50
|
+
maybeEnableAR(projectId),
|
|
43
51
|
]);
|
|
44
52
|
context.runtimeConfigEnabled = checkAPIsEnabled[1];
|
|
53
|
+
context.artifactRegistryEnabled = checkAPIsEnabled[3];
|
|
45
54
|
const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
|
|
46
55
|
context.firebaseConfig = firebaseConfig;
|
|
47
56
|
const runtimeConfig = await prepareFunctionsUpload_1.getFunctionsConfig(context);
|
|
@@ -104,8 +113,9 @@ async function prepare(context, options, payload) {
|
|
|
104
113
|
return functionsDeployHelper_1.functionMatchesAnyGroup(endpoint, context.filters);
|
|
105
114
|
});
|
|
106
115
|
const haveBackend = await backend.existingBackend(context);
|
|
116
|
+
await checkIam_1.ensureServiceAgentRoles(projectId, wantBackend, haveBackend);
|
|
107
117
|
inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
|
|
108
|
-
await triggerRegionHelper_1.
|
|
118
|
+
await triggerRegionHelper_1.ensureTriggerRegions(wantBackend);
|
|
109
119
|
await prompts_1.promptForFailurePolicies(options, matchingBackend, haveBackend);
|
|
110
120
|
await prompts_1.promptForMinInstances(options, matchingBackend, haveBackend);
|
|
111
121
|
await backend.checkAvailability(context, wantBackend);
|
|
@@ -157,6 +157,9 @@ class Fabricator {
|
|
|
157
157
|
throw new Error("Precondition failed");
|
|
158
158
|
}
|
|
159
159
|
const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl);
|
|
160
|
+
if (apiFunction.httpsTrigger) {
|
|
161
|
+
apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS";
|
|
162
|
+
}
|
|
160
163
|
apiFunction.sourceToken = await scraper.tokenPromise();
|
|
161
164
|
const resultFunction = await this.functionExecutor
|
|
162
165
|
.run(async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy =
|
|
3
|
+
exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const _ = require("lodash");
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
@@ -9,13 +9,8 @@ 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 eventTypes_1 = require("../../eventTypes");
|
|
12
13
|
const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
|
|
13
|
-
exports.GCS_EVENTS = new Set([
|
|
14
|
-
"google.cloud.storage.object.v1.finalized",
|
|
15
|
-
"google.cloud.storage.object.v1.archived",
|
|
16
|
-
"google.cloud.storage.object.v1.deleted",
|
|
17
|
-
"google.cloud.storage.object.v1.metadataUpdated",
|
|
18
|
-
]);
|
|
19
14
|
function removeInspectOptions(options) {
|
|
20
15
|
return options.filter((opt) => !opt.startsWith("--inspect"));
|
|
21
16
|
}
|
|
@@ -64,7 +59,6 @@ async function discoverBackend(projectId, sourceDir, runtime, configValues, envs
|
|
|
64
59
|
}
|
|
65
60
|
exports.discoverBackend = discoverBackend;
|
|
66
61
|
function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
67
|
-
var _a;
|
|
68
62
|
Object.freeze(annotation);
|
|
69
63
|
for (const region of annotation.regions || [api.functionsDefaultRegion]) {
|
|
70
64
|
let triggered;
|
|
@@ -99,7 +93,7 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
|
99
93
|
retry: !!annotation.failurePolicy,
|
|
100
94
|
},
|
|
101
95
|
};
|
|
102
|
-
if (
|
|
96
|
+
if (eventTypes_1.STORAGE_V2_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
|
|
103
97
|
triggered.eventTrigger.eventFilters = {
|
|
104
98
|
bucket: annotation.eventTrigger.resource,
|
|
105
99
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
4
|
+
const backend = require("../backend");
|
|
5
|
+
const storage_1 = require("./storage");
|
|
6
|
+
const noop = () => Promise.resolve();
|
|
7
|
+
exports.NoOpService = {
|
|
8
|
+
name: "noop",
|
|
9
|
+
api: "",
|
|
10
|
+
requiredProjectBindings: undefined,
|
|
11
|
+
ensureTriggerRegion: noop,
|
|
12
|
+
};
|
|
13
|
+
exports.PubSubService = {
|
|
14
|
+
name: "pubsub",
|
|
15
|
+
api: "pubsub.googleapis.com",
|
|
16
|
+
requiredProjectBindings: undefined,
|
|
17
|
+
ensureTriggerRegion: noop,
|
|
18
|
+
};
|
|
19
|
+
exports.StorageService = {
|
|
20
|
+
name: "storage",
|
|
21
|
+
api: "storage.googleapis.com",
|
|
22
|
+
requiredProjectBindings: storage_1.obtainStorageBindings,
|
|
23
|
+
ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
|
|
24
|
+
};
|
|
25
|
+
exports.EVENT_SERVICE_MAPPING = {
|
|
26
|
+
"google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
|
|
27
|
+
"google.cloud.storage.object.v1.finalized": exports.StorageService,
|
|
28
|
+
"google.cloud.storage.object.v1.archived": exports.StorageService,
|
|
29
|
+
"google.cloud.storage.object.v1.deleted": exports.StorageService,
|
|
30
|
+
"google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
|
|
31
|
+
};
|
|
32
|
+
function serviceForEndpoint(endpoint) {
|
|
33
|
+
if (!backend.isEventTriggered(endpoint)) {
|
|
34
|
+
return exports.NoOpService;
|
|
35
|
+
}
|
|
36
|
+
return exports.EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || exports.NoOpService;
|
|
37
|
+
}
|
|
38
|
+
exports.serviceForEndpoint = serviceForEndpoint;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
|
|
4
|
+
const storage = require("../../../gcp/storage");
|
|
5
|
+
const logger_1 = require("../../../logger");
|
|
6
|
+
const error_1 = require("../../../error");
|
|
7
|
+
const location_1 = require("../../../gcp/location");
|
|
8
|
+
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
9
|
+
async function obtainStorageBindings(projectId, existingPolicy) {
|
|
10
|
+
const storageResponse = await storage.getServiceAccount(projectId);
|
|
11
|
+
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
12
|
+
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
|
|
13
|
+
if (!pubsubBinding) {
|
|
14
|
+
pubsubBinding = {
|
|
15
|
+
role: PUBSUB_PUBLISHER_ROLE,
|
|
16
|
+
members: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!pubsubBinding.members.find((m) => m === storageServiceAgent)) {
|
|
20
|
+
pubsubBinding.members.push(storageServiceAgent);
|
|
21
|
+
}
|
|
22
|
+
return [pubsubBinding];
|
|
23
|
+
}
|
|
24
|
+
exports.obtainStorageBindings = obtainStorageBindings;
|
|
25
|
+
async function ensureStorageTriggerRegion(endpoint, eventTrigger) {
|
|
26
|
+
if (!eventTrigger.region) {
|
|
27
|
+
logger_1.logger.debug("Looking up bucket region for the storage event trigger");
|
|
28
|
+
try {
|
|
29
|
+
const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
|
|
30
|
+
eventTrigger.region = bucket.location.toLowerCase();
|
|
31
|
+
logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new error_1.FirebaseError("Can't find the storage bucket region", { original: err });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (endpoint.region !== eventTrigger.region &&
|
|
38
|
+
eventTrigger.region !== "us-central1" &&
|
|
39
|
+
!location_1.regionInLocation(endpoint.region, eventTrigger.region)) {
|
|
40
|
+
throw new error_1.FirebaseError(`A function in region ${endpoint.region} cannot listen to a bucket in region ${eventTrigger.region}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
|
|
@@ -1,40 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ensureTriggerRegions = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const logger_1 = require("../../logger");
|
|
8
|
-
const noop = () => Promise.resolve();
|
|
9
|
-
const LOOKUP_BY_EVENT_TYPE = {
|
|
10
|
-
"google.cloud.pubsub.topic.v1.messagePublished": noop,
|
|
11
|
-
"google.cloud.storage.object.v1.finalized": lookupBucketRegion,
|
|
12
|
-
"google.cloud.storage.object.v1.archived": lookupBucketRegion,
|
|
13
|
-
"google.cloud.storage.object.v1.deleted": lookupBucketRegion,
|
|
14
|
-
"google.cloud.storage.object.v1.metadataUpdated": lookupBucketRegion,
|
|
15
|
-
};
|
|
16
|
-
async function lookupMissingTriggerRegions(want) {
|
|
5
|
+
const services_1 = require("./services");
|
|
6
|
+
async function ensureTriggerRegions(want) {
|
|
17
7
|
const regionLookups = [];
|
|
18
8
|
for (const ep of backend.allEndpoints(want)) {
|
|
19
|
-
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)
|
|
9
|
+
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
|
|
20
10
|
continue;
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
if (!lookup) {
|
|
24
|
-
logger_1.logger.debug("Don't know how to look up trigger region for event type", ep.eventTrigger.eventType, ". Deploy will fail unless this event type is global");
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
regionLookups.push(lookup(ep));
|
|
12
|
+
regionLookups.push(services_1.serviceForEndpoint(ep).ensureTriggerRegion(ep, ep.eventTrigger));
|
|
28
13
|
}
|
|
29
14
|
await Promise.all(regionLookups);
|
|
30
15
|
}
|
|
31
|
-
exports.
|
|
32
|
-
async function lookupBucketRegion(endpoint) {
|
|
33
|
-
try {
|
|
34
|
-
const bucket = await storage.getBucket(endpoint.eventTrigger.eventFilters.bucket);
|
|
35
|
-
endpoint.eventTrigger.region = bucket.location.toLowerCase();
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
throw new error_1.FirebaseError("Can't find the storage bucket region", { original: err });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
16
|
+
exports.ensureTriggerRegions = ensureTriggerRegions;
|
|
@@ -1010,7 +1010,7 @@ function signInWithEmailLink(state, reqBody) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
}
|
|
1012
1012
|
function signInWithIdp(state, reqBody) {
|
|
1013
|
-
var _a, _b;
|
|
1013
|
+
var _a, _b, _c;
|
|
1014
1014
|
errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
|
|
1015
1015
|
errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
|
|
1016
1016
|
if (reqBody.returnRefreshToken) {
|
|
@@ -1041,7 +1041,16 @@ function signInWithIdp(state, reqBody) {
|
|
|
1041
1041
|
throw new errors_1.NotImplementedError("The Auth Emulator only supports sign-in with credentials (id_token required).");
|
|
1042
1042
|
}
|
|
1043
1043
|
}
|
|
1044
|
-
let
|
|
1044
|
+
let samlResponse;
|
|
1045
|
+
let signInAttributes = undefined;
|
|
1046
|
+
if (normalizedUri.searchParams.get("SAMLResponse")) {
|
|
1047
|
+
samlResponse = JSON.parse(normalizedUri.searchParams.get("SAMLResponse"));
|
|
1048
|
+
signInAttributes = (_b = samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.attributeStatements;
|
|
1049
|
+
errors_1.assert(samlResponse.assertion, "INVALID_IDP_RESPONSE ((Missing assertion in SAMLResponse.))");
|
|
1050
|
+
errors_1.assert(samlResponse.assertion.subject, "INVALID_IDP_RESPONSE ((Missing assertion.subject in SAMLResponse.))");
|
|
1051
|
+
errors_1.assert(samlResponse.assertion.subject.nameId, "INVALID_IDP_RESPONSE ((Missing assertion.subject.nameId in SAMLResponse.))");
|
|
1052
|
+
}
|
|
1053
|
+
let { response, rawId } = fakeFetchUserInfoFromIdp(providerId, claims, samlResponse);
|
|
1045
1054
|
response.oauthAccessToken =
|
|
1046
1055
|
oauthAccessToken || `FirebaseAuthEmulatorFakeAccessToken_${providerId}`;
|
|
1047
1056
|
response.oauthIdToken = oauthIdToken;
|
|
@@ -1101,12 +1110,12 @@ function signInWithIdp(state, reqBody) {
|
|
|
1101
1110
|
if (state instanceof state_1.TenantProjectState) {
|
|
1102
1111
|
response.tenantId = state.tenantId;
|
|
1103
1112
|
}
|
|
1104
|
-
if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((
|
|
1113
|
+
if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_c = user.mfaInfo) === null || _c === void 0 ? void 0 : _c.length)) {
|
|
1105
1114
|
return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
|
|
1106
1115
|
}
|
|
1107
1116
|
else {
|
|
1108
1117
|
user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
|
|
1109
|
-
return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId));
|
|
1118
|
+
return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId, { signInAttributes }));
|
|
1110
1119
|
}
|
|
1111
1120
|
}
|
|
1112
1121
|
function signInWithPassword(state, reqBody) {
|
|
@@ -1391,7 +1400,7 @@ function redactPasswordHash(user) {
|
|
|
1391
1400
|
function hashPassword(password, salt) {
|
|
1392
1401
|
return `fakeHash:salt=${salt}:password=${password}`;
|
|
1393
1402
|
}
|
|
1394
|
-
function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, } = {}) {
|
|
1403
|
+
function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, signInAttributes, } = {}) {
|
|
1395
1404
|
user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
|
|
1396
1405
|
const usageMode = state.usageMode === state_1.UsageMode.PASSTHROUGH ? "passthrough" : undefined;
|
|
1397
1406
|
const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
|
|
@@ -1404,6 +1413,7 @@ function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, }
|
|
|
1404
1413
|
secondFactor,
|
|
1405
1414
|
usageMode,
|
|
1406
1415
|
tenantId,
|
|
1416
|
+
signInAttributes,
|
|
1407
1417
|
});
|
|
1408
1418
|
const refreshToken = state.usageMode === state_1.UsageMode.DEFAULT
|
|
1409
1419
|
? state.createRefreshTokenFor(user, signInProvider, {
|
|
@@ -1435,7 +1445,7 @@ function parseIdToken(state, idToken) {
|
|
|
1435
1445
|
const signInProvider = decoded.payload.firebase.sign_in_provider;
|
|
1436
1446
|
return { user, signInProvider, payload: decoded.payload };
|
|
1437
1447
|
}
|
|
1438
|
-
function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, }) {
|
|
1448
|
+
function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, signInAttributes, }) {
|
|
1439
1449
|
const identities = {};
|
|
1440
1450
|
if (user.email) {
|
|
1441
1451
|
identities["email"] = [user.email];
|
|
@@ -1459,6 +1469,7 @@ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraC
|
|
|
1459
1469
|
sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
|
|
1460
1470
|
usage_mode: usageMode,
|
|
1461
1471
|
tenant: tenantId,
|
|
1472
|
+
sign_in_attributes: signInAttributes,
|
|
1462
1473
|
} });
|
|
1463
1474
|
const jwtStr = jsonwebtoken_1.sign(customPayloadFields, "", {
|
|
1464
1475
|
algorithm: "none",
|
|
@@ -1603,7 +1614,8 @@ function parseClaims(idTokenOrJsonClaims) {
|
|
|
1603
1614
|
errors_1.assert(typeof claims.sub === "string", 'INVALID_IDP_RESPONSE : ((The "sub" field must be a string.))');
|
|
1604
1615
|
return claims;
|
|
1605
1616
|
}
|
|
1606
|
-
function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
1617
|
+
function fakeFetchUserInfoFromIdp(providerId, claims, samlResponse) {
|
|
1618
|
+
var _a, _b, _c, _d, _e;
|
|
1607
1619
|
const rawId = claims.sub;
|
|
1608
1620
|
const email = claims.email ? utils_1.canonicalizeEmailAddress(claims.email) : undefined;
|
|
1609
1621
|
const emailVerified = !!claims.email_verified;
|
|
@@ -1620,7 +1632,7 @@ function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
|
1620
1632
|
emailVerified,
|
|
1621
1633
|
photoUrl,
|
|
1622
1634
|
};
|
|
1623
|
-
let federatedId;
|
|
1635
|
+
let federatedId = rawId;
|
|
1624
1636
|
switch (providerId) {
|
|
1625
1637
|
case "google.com": {
|
|
1626
1638
|
federatedId = `https://accounts.google.com/${rawId}`;
|
|
@@ -1643,8 +1655,14 @@ function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
|
1643
1655
|
});
|
|
1644
1656
|
break;
|
|
1645
1657
|
}
|
|
1658
|
+
case (_a = providerId.match(/^saml\./)) === null || _a === void 0 ? void 0 : _a.input:
|
|
1659
|
+
const nameId = (_c = (_b = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.subject) === null || _c === void 0 ? void 0 : _c.nameId;
|
|
1660
|
+
response.email = nameId && utils_1.isValidEmailAddress(nameId) ? nameId : response.email;
|
|
1661
|
+
response.emailVerified = true;
|
|
1662
|
+
response.rawUserInfo = JSON.stringify((_d = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _d === void 0 ? void 0 : _d.attributeStatements);
|
|
1663
|
+
break;
|
|
1664
|
+
case (_e = providerId.match(/^oidc\./)) === null || _e === void 0 ? void 0 : _e.input:
|
|
1646
1665
|
default:
|
|
1647
|
-
federatedId = rawId;
|
|
1648
1666
|
response.rawUserInfo = JSON.stringify(claims);
|
|
1649
1667
|
break;
|
|
1650
1668
|
}
|
|
@@ -33,6 +33,7 @@ var firebaseAppId = query.get('appId');
|
|
|
33
33
|
var apn = query.get('apn');
|
|
34
34
|
var ibi = query.get('ibi');
|
|
35
35
|
var appIdentifier = apn || ibi;
|
|
36
|
+
var isSamlProvider = !!providerId.match(/^saml\./);
|
|
36
37
|
assert(
|
|
37
38
|
appName || clientId || firebaseAppId || appIdentifier,
|
|
38
39
|
'Missing one of appName / clientId / appId / apn / ibi query params.'
|
|
@@ -172,6 +173,19 @@ function finishWithUser(urlEncodedIdToken) {
|
|
|
172
173
|
// Avoid URLSearchParams for browser compatibility.
|
|
173
174
|
url += '?providerId=' + encodeURIComponent(providerId);
|
|
174
175
|
url += '&id_token=' + urlEncodedIdToken;
|
|
176
|
+
|
|
177
|
+
// Save reasonable defaults for SAML providers
|
|
178
|
+
if (isSamlProvider) {
|
|
179
|
+
var email = document.getElementById('email-input').value;
|
|
180
|
+
url += '&SAMLResponse=' + encodeURIComponent(JSON.stringify({
|
|
181
|
+
assertion: {
|
|
182
|
+
subject: {
|
|
183
|
+
nameId: email,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
saveAuthEvent({
|
|
176
190
|
type: authType,
|
|
177
191
|
eventId: eventId,
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensure = exports.enable = exports.check = exports.POLL_SETTINGS = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const cli_color_1 = require("cli-color");
|
|
6
5
|
const track = require("./track");
|
|
7
|
-
const
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
const apiv2_1 = require("./apiv2");
|
|
8
8
|
const utils = require("./utils");
|
|
9
9
|
const error_1 = require("./error");
|
|
10
10
|
exports.POLL_SETTINGS = {
|
|
11
11
|
pollInterval: 10000,
|
|
12
12
|
pollsBeforeRetry: 12,
|
|
13
13
|
};
|
|
14
|
+
const apiClient = new apiv2_1.Client({
|
|
15
|
+
urlPrefix: api_1.serviceUsageOrigin,
|
|
16
|
+
apiVersion: "v1",
|
|
17
|
+
});
|
|
14
18
|
async function check(projectId, apiName, prefix, silent = false) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
origin: api.serviceUsageOrigin,
|
|
18
|
-
});
|
|
19
|
-
const isEnabled = _.get(response.body, "state") === "ENABLED";
|
|
19
|
+
const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`);
|
|
20
|
+
const isEnabled = res.body.state === "ENABLED";
|
|
20
21
|
if (isEnabled && !silent) {
|
|
21
22
|
utils.logLabeledSuccess(prefix, `required API ${cli_color_1.bold(apiName)} is enabled`);
|
|
22
23
|
}
|
|
@@ -25,10 +26,7 @@ async function check(projectId, apiName, prefix, silent = false) {
|
|
|
25
26
|
exports.check = check;
|
|
26
27
|
async function enable(projectId, apiName) {
|
|
27
28
|
try {
|
|
28
|
-
await
|
|
29
|
-
auth: true,
|
|
30
|
-
origin: api.serviceUsageOrigin,
|
|
31
|
-
});
|
|
29
|
+
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`);
|
|
32
30
|
}
|
|
33
31
|
catch (err) {
|
|
34
32
|
if (error_1.isBillingError(err)) {
|
|
@@ -49,7 +47,7 @@ async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRe
|
|
|
49
47
|
});
|
|
50
48
|
const isEnabled = await check(projectId, apiName, prefix, silent);
|
|
51
49
|
if (isEnabled) {
|
|
52
|
-
track("api_enabled", apiName);
|
|
50
|
+
void track("api_enabled", apiName);
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
if (!silent) {
|
|
@@ -4,6 +4,7 @@ exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFun
|
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const logger_1 = require("../logger");
|
|
7
|
+
const previews_1 = require("../previews");
|
|
7
8
|
const api = require("../api");
|
|
8
9
|
const backend = require("../deploy/functions/backend");
|
|
9
10
|
const utils = require("../utils");
|
|
@@ -50,8 +51,12 @@ async function createFunction(cloudFunction) {
|
|
|
50
51
|
const apiPath = cloudFunction.name.substring(0, cloudFunction.name.lastIndexOf("/"));
|
|
51
52
|
const endpoint = `/${exports.API_VERSION}/${apiPath}`;
|
|
52
53
|
try {
|
|
54
|
+
const headers = {};
|
|
55
|
+
if (previews_1.previews.artifactregistry) {
|
|
56
|
+
headers["X-Firebase-Artifact-Registry"] = "optin";
|
|
57
|
+
}
|
|
53
58
|
const res = await api.request("POST", endpoint, {
|
|
54
|
-
headers
|
|
59
|
+
headers,
|
|
55
60
|
auth: true,
|
|
56
61
|
data: cloudFunction,
|
|
57
62
|
origin: api.functionsOrigin,
|
|
@@ -146,8 +151,12 @@ async function updateFunction(cloudFunction) {
|
|
|
146
151
|
const endpoint = `/${exports.API_VERSION}/${cloudFunction.name}`;
|
|
147
152
|
const fieldMasks = proto.fieldMasks(cloudFunction, "labels", "environmentVariables");
|
|
148
153
|
try {
|
|
154
|
+
const headers = {};
|
|
155
|
+
if (previews_1.previews.artifactregistry) {
|
|
156
|
+
headers["X-Firebase-Artifact-Registry"] = "optin";
|
|
157
|
+
}
|
|
149
158
|
const res = await api.request("PATCH", endpoint, {
|
|
150
|
-
headers
|
|
159
|
+
headers,
|
|
151
160
|
qs: {
|
|
152
161
|
updateMask: fieldMasks.join(","),
|
|
153
162
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.PUBSUB_PUBLISH_EVENT = exports.API_VERSION = void 0;
|
|
3
|
+
exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.megabytes = exports.PUBSUB_PUBLISH_EVENT = exports.API_VERSION = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const apiv2_1 = require("../apiv2");
|
|
6
6
|
const error_1 = require("../error");
|
|
@@ -17,6 +17,35 @@ const client = new apiv2_1.Client({
|
|
|
17
17
|
apiVersion: exports.API_VERSION,
|
|
18
18
|
});
|
|
19
19
|
exports.PUBSUB_PUBLISH_EVENT = "google.cloud.pubsub.topic.v1.messagePublished";
|
|
20
|
+
const BYTES_PER_UNIT = {
|
|
21
|
+
"": 1,
|
|
22
|
+
k: 1e3,
|
|
23
|
+
M: 1e6,
|
|
24
|
+
G: 1e9,
|
|
25
|
+
T: 1e12,
|
|
26
|
+
Ki: 1 << 10,
|
|
27
|
+
Mi: 1 << 20,
|
|
28
|
+
Gi: 1 << 30,
|
|
29
|
+
Ti: 1 << 40,
|
|
30
|
+
};
|
|
31
|
+
function megabytes(memory) {
|
|
32
|
+
const re = /^([0-9]+(\.[0-9]*)?)(Ki|Mi|Gi|Ti|k|M|G|T|([eE]([0-9]+)))?$/;
|
|
33
|
+
const matches = re.exec(memory);
|
|
34
|
+
if (!matches) {
|
|
35
|
+
throw new Error(`Invalid memory quantity "${memory}""`);
|
|
36
|
+
}
|
|
37
|
+
const quantity = Number.parseFloat(matches[1]);
|
|
38
|
+
let bytes;
|
|
39
|
+
if (matches[5]) {
|
|
40
|
+
bytes = quantity * Math.pow(10, Number.parseFloat(matches[5]));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const suffix = matches[3] || "";
|
|
44
|
+
bytes = quantity * BYTES_PER_UNIT[suffix];
|
|
45
|
+
}
|
|
46
|
+
return bytes / 1e6;
|
|
47
|
+
}
|
|
48
|
+
exports.megabytes = megabytes;
|
|
20
49
|
function functionsOpLogReject(funcName, type, err) {
|
|
21
50
|
var _a, _b;
|
|
22
51
|
if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 429) {
|
|
@@ -136,7 +165,8 @@ function functionFromEndpoint(endpoint, source) {
|
|
|
136
165
|
serviceConfig: {},
|
|
137
166
|
};
|
|
138
167
|
proto.copyIfPresent(gcfFunction, endpoint, "labels");
|
|
139
|
-
proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "
|
|
168
|
+
proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "vpcConnector", "vpcConnectorEgressSettings", "serviceAccountEmail", "ingressSettings");
|
|
169
|
+
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "availableMemory", "availableMemoryMb", (mb) => `${mb}M`);
|
|
140
170
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "timeoutSeconds", "timeout", proto.secondsFromDuration);
|
|
141
171
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances");
|
|
142
172
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances");
|
|
@@ -153,6 +183,7 @@ function functionFromEndpoint(endpoint, source) {
|
|
|
153
183
|
gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
|
|
154
184
|
}
|
|
155
185
|
}
|
|
186
|
+
proto.renameIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "triggerRegion", "region");
|
|
156
187
|
if (endpoint.eventTrigger.retry) {
|
|
157
188
|
logger_1.logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id);
|
|
158
189
|
}
|
|
@@ -207,7 +238,8 @@ function endpointFromFunction(gcfFunction) {
|
|
|
207
238
|
const endpoint = Object.assign(Object.assign({ platform: "gcfv2", id,
|
|
208
239
|
project,
|
|
209
240
|
region }, trigger), { entryPoint: gcfFunction.buildConfig.entryPoint, runtime: gcfFunction.buildConfig.runtime, uri: gcfFunction.serviceConfig.uri });
|
|
210
|
-
proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "
|
|
241
|
+
proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
|
|
242
|
+
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "availableMemoryMb", "availableMemory", megabytes);
|
|
211
243
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "timeout", "timeoutSeconds", proto.durationFromSeconds);
|
|
212
244
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount");
|
|
213
245
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount");
|
package/lib/gcp/docker.js
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Client = void 0;
|
|
3
|
+
exports.Client = exports.GCR_SUBDOMAIN_MAPPING = void 0;
|
|
4
4
|
const error_1 = require("../error");
|
|
5
5
|
const api = require("../apiv2");
|
|
6
|
+
exports.GCR_SUBDOMAIN_MAPPING = {
|
|
7
|
+
"us-west1": "us",
|
|
8
|
+
"us-west2": "us",
|
|
9
|
+
"us-west3": "us",
|
|
10
|
+
"us-west4": "us",
|
|
11
|
+
"us-central1": "us",
|
|
12
|
+
"us-central2": "us",
|
|
13
|
+
"us-east1": "us",
|
|
14
|
+
"us-east4": "us",
|
|
15
|
+
"northamerica-northeast1": "us",
|
|
16
|
+
"southamerica-east1": "us",
|
|
17
|
+
"europe-west1": "eu",
|
|
18
|
+
"europe-west2": "eu",
|
|
19
|
+
"europe-west3": "eu",
|
|
20
|
+
"europe-west4": "eu",
|
|
21
|
+
"europe-west5": "eu",
|
|
22
|
+
"europe-west6": "eu",
|
|
23
|
+
"europe-central2": "eu",
|
|
24
|
+
"europe-north1": "eu",
|
|
25
|
+
"asia-east1": "asia",
|
|
26
|
+
"asia-east2": "asia",
|
|
27
|
+
"asia-northeast1": "asia",
|
|
28
|
+
"asia-northeast2": "asia",
|
|
29
|
+
"asia-northeast3": "asia",
|
|
30
|
+
"asia-south1": "asia",
|
|
31
|
+
"asia-southeast2": "asia",
|
|
32
|
+
"australia-southeast1": "asia",
|
|
33
|
+
};
|
|
6
34
|
function isErrors(response) {
|
|
7
35
|
return !!response && Object.prototype.hasOwnProperty.call(response, "errors");
|
|
8
36
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.regionInLocation = exports.DUAL_REGION_MAPPING = exports.MULTI_REGION_MAPPING = void 0;
|
|
4
|
+
exports.MULTI_REGION_MAPPING = {
|
|
5
|
+
"us-central1": "us",
|
|
6
|
+
"us-east1": "us",
|
|
7
|
+
"us-east4": "us",
|
|
8
|
+
"us-west1": "us",
|
|
9
|
+
"us-west2": "us",
|
|
10
|
+
"us-west3": "us",
|
|
11
|
+
"us-west4": "us",
|
|
12
|
+
"europe-central2": "eu",
|
|
13
|
+
"europe-north1": "eu",
|
|
14
|
+
"europe-west1": "eu",
|
|
15
|
+
"europe-west3": "eu",
|
|
16
|
+
"europe-west4": "eu",
|
|
17
|
+
"europe-west5": "eu",
|
|
18
|
+
"asia-east1": "asia",
|
|
19
|
+
"asia-east2": "asia",
|
|
20
|
+
"asia-northeast1": "asia",
|
|
21
|
+
"asia-northeast2": "asia",
|
|
22
|
+
"asia-northeast3": "asia",
|
|
23
|
+
"asia-south1": "asia",
|
|
24
|
+
"asia-south2": "asia",
|
|
25
|
+
"asia-southeast1": "asia",
|
|
26
|
+
"asia-southeast2": "asia",
|
|
27
|
+
};
|
|
28
|
+
exports.DUAL_REGION_MAPPING = {
|
|
29
|
+
"asia-northeast1": "asia1",
|
|
30
|
+
"asia-northeast2": "asia1",
|
|
31
|
+
"europe-north1": "eur4",
|
|
32
|
+
"europe-west4": "eur4",
|
|
33
|
+
"us-central1": "nam4",
|
|
34
|
+
"us-east1": "nam4",
|
|
35
|
+
};
|
|
36
|
+
function regionInLocation(region, location) {
|
|
37
|
+
region = region.toLowerCase();
|
|
38
|
+
location = location.toLowerCase();
|
|
39
|
+
if (exports.MULTI_REGION_MAPPING[region] === location || exports.DUAL_REGION_MAPPING[region] === location) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
exports.regionInLocation = regionInLocation;
|
package/lib/gcp/storage.js
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getServiceAccount = exports.getBucket = exports.deleteObject = exports.uploadObject = exports.upload = exports.getDefaultBucket = void 0;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const api = require("../api");
|
|
6
|
+
const logger_1 = require("../logger");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
async function getDefaultBucket(projectId) {
|
|
9
|
+
try {
|
|
10
|
+
const resp = await api.request("GET", "/v1/apps/" + projectId, {
|
|
11
|
+
auth: true,
|
|
12
|
+
origin: api.appengineOrigin,
|
|
13
|
+
});
|
|
13
14
|
if (resp.body.defaultBucket === "undefined") {
|
|
14
|
-
logger.debug("Default storage bucket is undefined.");
|
|
15
|
-
|
|
15
|
+
logger_1.logger.debug("Default storage bucket is undefined.");
|
|
16
|
+
throw new error_1.FirebaseError("Your project is being set up. Please wait a minute before deploying again.");
|
|
16
17
|
}
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
return resp.body.defaultBucket;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
logger_1.logger.info("\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support.");
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
exports.getDefaultBucket = getDefaultBucket;
|
|
26
|
+
async function upload(source, uploadUrl, extraHeaders) {
|
|
24
27
|
const url = new URL(uploadUrl);
|
|
25
28
|
const result = await api.request("PUT", url.pathname + url.search, {
|
|
26
29
|
data: source.stream,
|
|
@@ -33,9 +36,10 @@ async function _uploadSource(source, uploadUrl, extraHeaders) {
|
|
|
33
36
|
generation: result.response.headers["x-goog-generation"],
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
|
-
|
|
39
|
+
exports.upload = upload;
|
|
40
|
+
async function uploadObject(source, bucketName) {
|
|
37
41
|
if (path.extname(source.file) !== ".zip") {
|
|
38
|
-
throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`);
|
|
42
|
+
throw new error_1.FirebaseError(`Expected a file name ending in .zip, got ${source.file}`);
|
|
39
43
|
}
|
|
40
44
|
const location = `/${bucketName}/${path.basename(source.file)}`;
|
|
41
45
|
const result = await api.request("PUT", location, {
|
|
@@ -55,13 +59,15 @@ async function _uploadObject(source, bucketName) {
|
|
|
55
59
|
generation: result.response.headers["x-goog-generation"],
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
|
-
|
|
62
|
+
exports.uploadObject = uploadObject;
|
|
63
|
+
function deleteObject(location) {
|
|
59
64
|
return api.request("DELETE", location, {
|
|
60
65
|
auth: true,
|
|
61
66
|
origin: api.storageOrigin,
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
exports.deleteObject = deleteObject;
|
|
70
|
+
async function getBucket(bucketName) {
|
|
65
71
|
try {
|
|
66
72
|
const result = await api.request("GET", `/storage/v1/b/${bucketName}`, {
|
|
67
73
|
auth: true,
|
|
@@ -70,16 +76,26 @@ async function _getBucket(bucketName) {
|
|
|
70
76
|
return result.body;
|
|
71
77
|
}
|
|
72
78
|
catch (err) {
|
|
73
|
-
logger.debug(err);
|
|
74
|
-
throw new FirebaseError("Failed to obtain the storage bucket", {
|
|
79
|
+
logger_1.logger.debug(err);
|
|
80
|
+
throw new error_1.FirebaseError("Failed to obtain the storage bucket", {
|
|
81
|
+
original: err,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.getBucket = getBucket;
|
|
86
|
+
async function getServiceAccount(projectId) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await api.request("GET", `/storage/v1/projects/${projectId}/serviceAccount`, {
|
|
89
|
+
auth: true,
|
|
90
|
+
origin: api.storageOrigin,
|
|
91
|
+
});
|
|
92
|
+
return response.body;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger_1.logger.debug(err);
|
|
96
|
+
throw new error_1.FirebaseError("Failed to obtain the Cloud Storage service agent", {
|
|
75
97
|
original: err,
|
|
76
98
|
});
|
|
77
99
|
}
|
|
78
100
|
}
|
|
79
|
-
|
|
80
|
-
getDefaultBucket: _getDefaultBucket,
|
|
81
|
-
deleteObject: _deleteObject,
|
|
82
|
-
upload: _uploadSource,
|
|
83
|
-
uploadObject: _uploadObject,
|
|
84
|
-
getBucket: _getBucket,
|
|
85
|
-
};
|
|
101
|
+
exports.getServiceAccount = getServiceAccount;
|
|
@@ -72,7 +72,7 @@ async function doSetup(setup, config, options) {
|
|
|
72
72
|
logger_1.logger.info(`but for now we'll just set up a default project.`);
|
|
73
73
|
logger_1.logger.info();
|
|
74
74
|
const projectFromRcFile = _.get(setup.rcfile, "projects.default");
|
|
75
|
-
if (projectFromRcFile) {
|
|
75
|
+
if (projectFromRcFile && !options.project) {
|
|
76
76
|
utils.logBullet(`.firebaserc already has a default project, using ${projectFromRcFile}.`);
|
|
77
77
|
const rcProject = await projects_1.getFirebaseProject(projectFromRcFile);
|
|
78
78
|
setup.projectId = rcProject.projectId;
|
|
@@ -81,6 +81,7 @@ async function doSetup(setup, config, options) {
|
|
|
81
81
|
}
|
|
82
82
|
let projectMetaData;
|
|
83
83
|
if (options.project) {
|
|
84
|
+
logger_1.logger.debug(`Using project from CLI flag: ${options.project}`);
|
|
84
85
|
projectMetaData = await projects_1.getFirebaseProject(options.project);
|
|
85
86
|
}
|
|
86
87
|
else {
|
package/lib/previews.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.previews = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const configstore_1 = require("./configstore");
|
|
6
|
-
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, dotenv: false }, configstore_1.configstore.get("previews"));
|
|
6
|
+
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, dotenv: false, artifactregistry: false }, configstore_1.configstore.get("previews"));
|
|
7
7
|
if (process.env.FIREBASE_CLI_PREVIEWS) {
|
|
8
8
|
process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach((feature) => {
|
|
9
9
|
if (lodash_1.has(exports.previews, feature)) {
|