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 CHANGED
@@ -1 +1,5 @@
1
- - `firebase deploy --only extensions` now supports project specifc .env files. When deploying to multiple projects, param values that are different between projects can be put in `extensions/${extensionInstanceId}.env.${projectIdOrAlias}` and common param values can be put in `extensions/${extensionInstanceId}.env`.
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 iam_1 = require("../../gcp/iam");
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 iam_1.testResourceIamPermissions("https://iam.googleapis.com", "v1", `projects/${projectId}/serviceAccounts/${saEmail}`, ["iam.serviceAccounts.actAs"]);
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 iam_1.testIamPermissions(context.projectId, [PERMISSION]);
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.ArtifactRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
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}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
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
- return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${func.id}`;
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 = exports.SUBDOMAIN_MAPPING[location] || "us";
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(exports.SUBDOMAIN_MAPPING);
177
+ locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
189
178
  }
190
- const invalidRegion = locations.find((loc) => !exports.SUBDOMAIN_MAPPING[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(exports.SUBDOMAIN_MAPPING));
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 `${exports.SUBDOMAIN_MAPPING[loc]}.${api_1.containerRegistryDomain}/${projectId}/gcf/${loc}`;
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(exports.SUBDOMAIN_MAPPING);
221
+ locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
233
222
  }
234
- const invalidRegion = locations.find((loc) => !exports.SUBDOMAIN_MAPPING[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(exports.SUBDOMAIN_MAPPING));
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, exports.SUBDOMAIN_MAPPING[loc]).rm(`${projectId}/gcf/${loc}`);
232
+ return getHelper(dockerHelpers, subdomain).rm(`${projectId}/gcf/${loc}`);
243
233
  }
244
234
  catch (err) {
245
- failedSubdomains.push(exports.SUBDOMAIN_MAPPING[loc]);
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
- ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions"),
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.lookupMissingTriggerRegions(wantBackend);
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 = exports.GCS_EVENTS = void 0;
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 (exports.GCS_EVENTS.has(((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || "")) {
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.lookupMissingTriggerRegions = void 0;
3
+ exports.ensureTriggerRegions = void 0;
4
4
  const backend = require("./backend");
5
- const storage = require("../../gcp/storage");
6
- const error_1 = require("../../error");
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) || ep.eventTrigger.region) {
9
+ if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
20
10
  continue;
21
11
  }
22
- const lookup = LOOKUP_BY_EVENT_TYPE[ep.eventTrigger.eventType];
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.lookupMissingTriggerRegions = lookupMissingTriggerRegions;
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 { response, rawId } = fakeFetchUserInfoFromIdp(providerId, claims);
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") && ((_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.length)) {
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,
@@ -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 api = require("./api");
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 response = await api.request("GET", `/v1/projects/${projectId}/services/${apiName}`, {
16
- auth: true,
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 api.request("POST", `/v1/projects/${projectId}/services/${apiName}:enable`, {
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: { "X-Firebase-Artifact-Registry": "optin" },
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: { "X-Firebase-Artifact-Registry": "optin" },
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, "availableMemoryMb", "environmentVariables", "vpcConnector", "vpcConnectorEgressSettings", "serviceAccountEmail", "ingressSettings");
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", "availableMemoryMb", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
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;
@@ -1,26 +1,29 @@
1
1
  "use strict";
2
- var path = require("path");
3
- var api = require("../api");
4
- const { logger } = require("../logger");
5
- var { FirebaseError } = require("../error");
6
- function _getDefaultBucket(projectId) {
7
- return api
8
- .request("GET", "/v1/apps/" + projectId, {
9
- auth: true,
10
- origin: api.appengineOrigin,
11
- })
12
- .then(function (resp) {
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
- return Promise.reject(new FirebaseError("Your project is being set up. Please wait a minute before deploying again."));
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 Promise.resolve(resp.body.defaultBucket);
18
- }, function (err) {
19
- 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.");
20
- return Promise.reject(err);
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
- async function _uploadSource(source, uploadUrl, extraHeaders) {
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
- async function _uploadObject(source, bucketName) {
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
- function _deleteObject(location) {
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
- async function _getBucket(bucketName) {
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
- module.exports = {
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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "9.23.0",
3
+ "version": "9.23.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {