firebase-tools 10.2.2 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/ext-configure.js +58 -4
- package/lib/commands/ext-export.js +4 -9
- package/lib/commands/ext-install.js +63 -4
- package/lib/commands/ext-uninstall.js +9 -0
- package/lib/commands/ext-update.js +55 -2
- package/lib/config.js +6 -3
- package/lib/deploy/extensions/planner.js +6 -6
- package/lib/deploy/functions/backend.js +10 -1
- package/lib/deploy/functions/checkIam.js +4 -4
- package/lib/deploy/functions/prepare.js +2 -1
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/planner.js +34 -20
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
- package/lib/deploy/functions/runtimes/node/index.js +27 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -8
- package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
- package/lib/deploy/functions/services/index.js +9 -1
- package/lib/deploy/functions/services/storage.js +10 -4
- package/lib/deploy/functions/triggerRegionHelper.js +1 -1
- package/lib/emulator/constants.js +1 -0
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/extensions/validation.js +37 -2
- package/lib/emulator/extensionsEmulator.js +44 -9
- package/lib/emulator/functionsEmulator.js +13 -8
- package/lib/emulator/functionsEmulatorShared.js +17 -10
- package/lib/emulator/storage/apis/firebase.js +312 -335
- package/lib/emulator/storage/apis/gcloud.js +238 -113
- package/lib/emulator/storage/crc.js +5 -1
- package/lib/emulator/storage/errors.js +9 -0
- package/lib/emulator/storage/files.js +161 -304
- package/lib/emulator/storage/index.js +27 -73
- package/lib/emulator/storage/metadata.js +63 -49
- package/lib/emulator/storage/multipart.js +62 -0
- package/lib/emulator/storage/persistence.js +78 -0
- package/lib/emulator/storage/rules/config.js +33 -0
- package/lib/emulator/storage/rules/manager.js +81 -0
- package/lib/emulator/storage/rules/utils.js +48 -0
- package/lib/emulator/storage/server.js +2 -2
- package/lib/emulator/storage/upload.js +106 -0
- package/lib/extensions/emulator/optionsHelper.js +35 -3
- package/lib/extensions/extensionsHelper.js +19 -10
- package/lib/extensions/manifest.js +109 -13
- package/lib/extensions/paramHelper.js +5 -4
- package/lib/functions/env.js +4 -6
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +18 -6
- package/lib/gcp/cloudfunctionsv2.js +30 -12
- package/lib/gcp/resourceManager.js +4 -4
- package/lib/serve/functions.js +2 -1
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/deploy/extensions/params.js +0 -42
- package/lib/deploy/functions/eventTypes.js +0 -10
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.
|
|
3
|
+
exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0;
|
|
4
4
|
const functionsDeployHelper_1 = require("../functionsDeployHelper");
|
|
5
|
-
const functionsDeployHelper_2 = require("../functionsDeployHelper");
|
|
6
5
|
const deploymentTool_1 = require("../../../deploymentTool");
|
|
7
6
|
const error_1 = require("../../../error");
|
|
8
7
|
const utils = require("../../../utils");
|
|
9
8
|
const backend = require("../backend");
|
|
10
|
-
const
|
|
11
|
-
function
|
|
12
|
-
const
|
|
9
|
+
const v2events = require("../../../functions/events/v2");
|
|
10
|
+
function calculateChangesets(want, have, keyFn, options) {
|
|
11
|
+
const toCreate = utils.groupBy(Object.keys(want)
|
|
13
12
|
.filter((id) => !have[id])
|
|
14
|
-
.map((id) => want[id]);
|
|
15
|
-
const
|
|
13
|
+
.map((id) => want[id]), keyFn);
|
|
14
|
+
const toDelete = utils.groupBy(Object.keys(have)
|
|
16
15
|
.filter((id) => !want[id])
|
|
17
16
|
.filter((id) => options.deleteAll || (0, deploymentTool_1.isFirebaseManaged)(have[id].labels || {}))
|
|
18
|
-
.map((id) => have[id]);
|
|
19
|
-
const
|
|
17
|
+
.map((id) => have[id]), keyFn);
|
|
18
|
+
const toUpdate = utils.groupBy(Object.keys(want)
|
|
20
19
|
.filter((id) => have[id])
|
|
21
|
-
.map((id) => calculateUpdate(want[id], have[id]));
|
|
22
|
-
|
|
20
|
+
.map((id) => calculateUpdate(want[id], have[id])), (eu) => keyFn(eu.endpoint));
|
|
21
|
+
const result = {};
|
|
22
|
+
const keys = new Set([
|
|
23
|
+
...Object.keys(toCreate),
|
|
24
|
+
...Object.keys(toDelete),
|
|
25
|
+
...Object.keys(toUpdate),
|
|
26
|
+
]);
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
result[key] = {
|
|
29
|
+
endpointsToCreate: toCreate[key] || [],
|
|
30
|
+
endpointsToUpdate: toUpdate[key] || [],
|
|
31
|
+
endpointsToDelete: toDelete[key] || [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
23
35
|
}
|
|
24
|
-
exports.
|
|
36
|
+
exports.calculateChangesets = calculateChangesets;
|
|
25
37
|
function calculateUpdate(want, have) {
|
|
26
38
|
checkForIllegalUpdate(want, have);
|
|
27
39
|
const update = {
|
|
@@ -37,7 +49,7 @@ function calculateUpdate(want, have) {
|
|
|
37
49
|
}
|
|
38
50
|
exports.calculateUpdate = calculateUpdate;
|
|
39
51
|
function createDeploymentPlan(want, have, options = {}) {
|
|
40
|
-
|
|
52
|
+
let deployment = {};
|
|
41
53
|
want = backend.matchingBackend(want, (endpoint) => {
|
|
42
54
|
return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, options.filters || []);
|
|
43
55
|
});
|
|
@@ -46,7 +58,8 @@ function createDeploymentPlan(want, have, options = {}) {
|
|
|
46
58
|
});
|
|
47
59
|
const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]);
|
|
48
60
|
for (const region of regions) {
|
|
49
|
-
|
|
61
|
+
const changesets = calculateChangesets(want.endpoints[region] || {}, have.endpoints[region] || {}, (e) => `${e.region}-${e.availableMemoryMb || "default"}`, options);
|
|
62
|
+
deployment = Object.assign(Object.assign({}, deployment), changesets);
|
|
50
63
|
}
|
|
51
64
|
if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) {
|
|
52
65
|
utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
|
|
@@ -90,6 +103,7 @@ function changedTriggerRegion(want, have) {
|
|
|
90
103
|
}
|
|
91
104
|
exports.changedTriggerRegion = changedTriggerRegion;
|
|
92
105
|
function changedV2PubSubTopic(want, have) {
|
|
106
|
+
var _a, _b;
|
|
93
107
|
if (want.platform !== "gcfv2") {
|
|
94
108
|
return false;
|
|
95
109
|
}
|
|
@@ -102,13 +116,13 @@ function changedV2PubSubTopic(want, have) {
|
|
|
102
116
|
if (!backend.isEventTriggered(have)) {
|
|
103
117
|
return false;
|
|
104
118
|
}
|
|
105
|
-
if (want.eventTrigger.eventType !==
|
|
119
|
+
if (want.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
|
|
106
120
|
return false;
|
|
107
121
|
}
|
|
108
|
-
if (have.eventTrigger.eventType !==
|
|
122
|
+
if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
|
|
109
123
|
return false;
|
|
110
124
|
}
|
|
111
|
-
return have
|
|
125
|
+
return (((_a = backend.findEventFilter(have, "topic")) === null || _a === void 0 ? void 0 : _a.value) !== ((_b = backend.findEventFilter(want, "topic")) === null || _b === void 0 ? void 0 : _b.value));
|
|
112
126
|
}
|
|
113
127
|
exports.changedV2PubSubTopic = changedV2PubSubTopic;
|
|
114
128
|
function upgradedScheduleFromV1ToV2(want, have) {
|
|
@@ -149,17 +163,17 @@ function checkForIllegalUpdate(want, have) {
|
|
|
149
163
|
const wantType = triggerType(want);
|
|
150
164
|
const haveType = triggerType(have);
|
|
151
165
|
if (wantType !== haveType) {
|
|
152
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
166
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
|
|
153
167
|
}
|
|
154
168
|
if (want.platform === "gcfv1" && have.platform === "gcfv2") {
|
|
155
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
169
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
|
|
156
170
|
}
|
|
157
171
|
exports.checkForV2Upgrade(want, have);
|
|
158
172
|
}
|
|
159
173
|
exports.checkForIllegalUpdate = checkForIllegalUpdate;
|
|
160
174
|
function checkForV2Upgrade(want, have) {
|
|
161
175
|
if (want.platform === "gcfv2" && have.platform === "gcfv1") {
|
|
162
|
-
throw new error_1.FirebaseError(`[${(0,
|
|
176
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.`);
|
|
163
177
|
}
|
|
164
178
|
}
|
|
165
179
|
exports.checkForV2Upgrade = checkForV2Upgrade;
|
|
@@ -88,13 +88,18 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
|
|
|
88
88
|
if (backend.isEventTriggered(ep)) {
|
|
89
89
|
(0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
|
|
90
90
|
(0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
|
|
91
|
-
eventFilters: "
|
|
91
|
+
eventFilters: "array",
|
|
92
92
|
eventType: "string",
|
|
93
93
|
retry: "boolean",
|
|
94
94
|
region: "string",
|
|
95
95
|
serviceAccountEmail: "string",
|
|
96
96
|
});
|
|
97
97
|
triggered = { eventTrigger: ep.eventTrigger };
|
|
98
|
+
for (const eventFilter of triggered.eventTrigger.eventFilters) {
|
|
99
|
+
if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) {
|
|
100
|
+
eventFilter.value = `projects/${project}/topics/${eventFilter.value}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
else if (backend.isHttpsTriggered(ep)) {
|
|
100
105
|
(0, parsing_1.assertKeyTypes)(prefix + ".httpsTrigger", ep.httpsTrigger, {
|
|
@@ -4,14 +4,20 @@ exports.Delegate = exports.tryCreateDelegate = void 0;
|
|
|
4
4
|
const util_1 = require("util");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const portfinder = require("portfinder");
|
|
8
|
+
const semver = require("semver");
|
|
7
9
|
const spawn = require("cross-spawn");
|
|
8
10
|
const node_fetch_1 = require("node-fetch");
|
|
9
11
|
const error_1 = require("../../../../error");
|
|
10
12
|
const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
|
|
11
13
|
const logger_1 = require("../../../../logger");
|
|
14
|
+
const previews_1 = require("../../../../previews");
|
|
15
|
+
const utils_1 = require("../../../../utils");
|
|
16
|
+
const discovery = require("../discovery");
|
|
12
17
|
const validate = require("./validate");
|
|
13
18
|
const versioning = require("./versioning");
|
|
14
19
|
const parseTriggers = require("./parseTriggers");
|
|
20
|
+
const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
|
|
15
21
|
async function tryCreateDelegate(context) {
|
|
16
22
|
const packageJsonPath = path.join(context.sourceDir, "package.json");
|
|
17
23
|
if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
|
|
@@ -77,6 +83,27 @@ class Delegate {
|
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
async discoverSpec(config, env) {
|
|
86
|
+
if (previews_1.previews.functionsv2) {
|
|
87
|
+
if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
|
|
88
|
+
(0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
|
|
89
|
+
`Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
|
|
90
|
+
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
91
|
+
}
|
|
92
|
+
let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
|
|
93
|
+
if (!discovered) {
|
|
94
|
+
const getPort = (0, util_1.promisify)(portfinder.getPort);
|
|
95
|
+
const port = await getPort();
|
|
96
|
+
const kill = await this.serve(port, env);
|
|
97
|
+
try {
|
|
98
|
+
discovered = await discovery.detectFromPort(port, this.projectId, this.runtime);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await kill();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
discovered.environmentVariables = env;
|
|
105
|
+
return discovered;
|
|
106
|
+
}
|
|
80
107
|
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
81
108
|
}
|
|
82
109
|
}
|
|
@@ -9,7 +9,7 @@ const logger_1 = require("../../../../logger");
|
|
|
9
9
|
const backend = require("../../backend");
|
|
10
10
|
const api = require("../../../../api");
|
|
11
11
|
const proto = require("../../../../gcp/proto");
|
|
12
|
-
const
|
|
12
|
+
const v2events = require("../../../../functions/events/v2");
|
|
13
13
|
const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
|
|
14
14
|
function removeInspectOptions(options) {
|
|
15
15
|
return options.filter((opt) => !opt.startsWith("--inspect"));
|
|
@@ -106,16 +106,32 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
|
106
106
|
triggered = {
|
|
107
107
|
eventTrigger: {
|
|
108
108
|
eventType: annotation.eventTrigger.eventType,
|
|
109
|
-
eventFilters:
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
eventFilters: [
|
|
110
|
+
{
|
|
111
|
+
attribute: "resource",
|
|
112
|
+
value: annotation.eventTrigger.resource,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
112
115
|
retry: !!annotation.failurePolicy,
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if (annotation.platform === "gcfv2") {
|
|
119
|
+
if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
|
|
120
|
+
triggered.eventTrigger.eventFilters = [
|
|
121
|
+
{
|
|
122
|
+
attribute: "topic",
|
|
123
|
+
value: annotation.eventTrigger.resource,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
|
|
128
|
+
triggered.eventTrigger.eventFilters = [
|
|
129
|
+
{
|
|
130
|
+
attribute: "bucket",
|
|
131
|
+
value: annotation.eventTrigger.resource,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
137
|
const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", id: annotation.name, region: region, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureFirebaseAlertsTriggerRegion = exports.obtainFirebaseAlertsBindings = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
|
|
4
|
+
const error_1 = require("../../../error");
|
|
5
|
+
exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
|
|
6
|
+
function obtainFirebaseAlertsBindings(projectNumber, existingPolicy) {
|
|
7
|
+
const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
8
|
+
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE);
|
|
9
|
+
if (!pubsubBinding) {
|
|
10
|
+
pubsubBinding = {
|
|
11
|
+
role: exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
|
|
12
|
+
members: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (!pubsubBinding.members.find((m) => m === pubsubServiceAgent)) {
|
|
16
|
+
pubsubBinding.members.push(pubsubServiceAgent);
|
|
17
|
+
}
|
|
18
|
+
return Promise.resolve([pubsubBinding]);
|
|
19
|
+
}
|
|
20
|
+
exports.obtainFirebaseAlertsBindings = obtainFirebaseAlertsBindings;
|
|
21
|
+
function ensureFirebaseAlertsTriggerRegion(endpoint) {
|
|
22
|
+
if (!endpoint.eventTrigger.region) {
|
|
23
|
+
endpoint.eventTrigger.region = "global";
|
|
24
|
+
}
|
|
25
|
+
if (endpoint.eventTrigger.region !== "global") {
|
|
26
|
+
throw new error_1.FirebaseError("A firebase alerts trigger must specify 'global' trigger location");
|
|
27
|
+
}
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
exports.ensureFirebaseAlertsTriggerRegion = ensureFirebaseAlertsTriggerRegion;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
3
|
+
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.FirebaseAlertsService = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
4
4
|
const backend = require("../backend");
|
|
5
5
|
const storage_1 = require("./storage");
|
|
6
|
+
const firebaseAlerts_1 = require("./firebaseAlerts");
|
|
6
7
|
const noop = () => Promise.resolve();
|
|
7
8
|
exports.NoOpService = {
|
|
8
9
|
name: "noop",
|
|
@@ -22,12 +23,19 @@ exports.StorageService = {
|
|
|
22
23
|
requiredProjectBindings: storage_1.obtainStorageBindings,
|
|
23
24
|
ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
|
|
24
25
|
};
|
|
26
|
+
exports.FirebaseAlertsService = {
|
|
27
|
+
name: "firebasealerts",
|
|
28
|
+
api: "logging.googleapis.com",
|
|
29
|
+
requiredProjectBindings: firebaseAlerts_1.obtainFirebaseAlertsBindings,
|
|
30
|
+
ensureTriggerRegion: firebaseAlerts_1.ensureFirebaseAlertsTriggerRegion,
|
|
31
|
+
};
|
|
25
32
|
exports.EVENT_SERVICE_MAPPING = {
|
|
26
33
|
"google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
|
|
27
34
|
"google.cloud.storage.object.v1.finalized": exports.StorageService,
|
|
28
35
|
"google.cloud.storage.object.v1.archived": exports.StorageService,
|
|
29
36
|
"google.cloud.storage.object.v1.deleted": exports.StorageService,
|
|
30
37
|
"google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
|
|
38
|
+
"google.firebase.firebasealerts.alerts.v1.published": exports.FirebaseAlertsService,
|
|
31
39
|
};
|
|
32
40
|
function serviceForEndpoint(endpoint) {
|
|
33
41
|
if (!backend.isEventTriggered(endpoint)) {
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
|
|
4
4
|
const storage = require("../../../gcp/storage");
|
|
5
|
+
const backend = require("../backend");
|
|
5
6
|
const logger_1 = require("../../../logger");
|
|
6
7
|
const error_1 = require("../../../error");
|
|
7
8
|
const location_1 = require("../../../gcp/location");
|
|
8
9
|
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
9
|
-
async function obtainStorageBindings(
|
|
10
|
-
const storageResponse = await storage.getServiceAccount(
|
|
10
|
+
async function obtainStorageBindings(projectNumber, existingPolicy) {
|
|
11
|
+
const storageResponse = await storage.getServiceAccount(projectNumber);
|
|
11
12
|
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
12
13
|
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
|
|
13
14
|
if (!pubsubBinding) {
|
|
@@ -22,11 +23,16 @@ async function obtainStorageBindings(projectId, existingPolicy) {
|
|
|
22
23
|
return [pubsubBinding];
|
|
23
24
|
}
|
|
24
25
|
exports.obtainStorageBindings = obtainStorageBindings;
|
|
25
|
-
async function ensureStorageTriggerRegion(endpoint
|
|
26
|
+
async function ensureStorageTriggerRegion(endpoint) {
|
|
27
|
+
const { eventTrigger } = endpoint;
|
|
26
28
|
if (!eventTrigger.region) {
|
|
27
29
|
logger_1.logger.debug("Looking up bucket region for the storage event trigger");
|
|
30
|
+
const bucketFilter = backend.findEventFilter(endpoint, "bucket");
|
|
31
|
+
if (!bucketFilter) {
|
|
32
|
+
throw new error_1.FirebaseError("Storage event trigger unexpectedly missing event filter with bucket attribute.");
|
|
33
|
+
}
|
|
28
34
|
try {
|
|
29
|
-
const bucket = await storage.getBucket(
|
|
35
|
+
const bucket = await storage.getBucket(bucketFilter.value);
|
|
30
36
|
eventTrigger.region = bucket.location.toLowerCase();
|
|
31
37
|
logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
|
|
32
38
|
}
|
|
@@ -9,7 +9,7 @@ async function ensureTriggerRegions(want) {
|
|
|
9
9
|
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
|
|
10
10
|
continue;
|
|
11
11
|
}
|
|
12
|
-
regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep
|
|
12
|
+
regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
|
|
13
13
|
}
|
|
14
14
|
await Promise.all(regionLookups);
|
|
15
15
|
}
|
|
@@ -90,6 +90,7 @@ class Constants {
|
|
|
90
90
|
}
|
|
91
91
|
exports.Constants = Constants;
|
|
92
92
|
Constants.FAKE_PROJECT_ID_PREFIX = "demo-";
|
|
93
|
+
Constants.FAKE_PROJECT_NUMBER = "0";
|
|
93
94
|
Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE = "fake-server";
|
|
94
95
|
Constants.FIRESTORE_EMULATOR_HOST = "FIRESTORE_EMULATOR_HOST";
|
|
95
96
|
Constants.FIREBASE_DATABASE_EMULATOR_HOST = "FIREBASE_DATABASE_EMULATOR_HOST";
|
|
@@ -33,6 +33,7 @@ const prompt_1 = require("../prompt");
|
|
|
33
33
|
const commandUtils_1 = require("./commandUtils");
|
|
34
34
|
const fsutils_1 = require("../fsutils");
|
|
35
35
|
const storage_1 = require("./storage");
|
|
36
|
+
const config_1 = require("./storage/rules/config");
|
|
36
37
|
const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
|
|
37
38
|
const auth_2 = require("../auth");
|
|
38
39
|
const extensionsEmulator_1 = require("./extensionsEmulator");
|
|
@@ -259,12 +260,15 @@ async function startAll(options, showUI = true) {
|
|
|
259
260
|
emulatableBackends.push({
|
|
260
261
|
functionsDir,
|
|
261
262
|
env: Object.assign({}, options.extDevEnv),
|
|
263
|
+
secretEnv: [],
|
|
262
264
|
predefinedTriggers: options.extDevTriggers,
|
|
263
265
|
nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || options.config.get("functions.runtime")),
|
|
264
266
|
});
|
|
265
267
|
}
|
|
266
268
|
if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
|
|
267
|
-
const projectNumber =
|
|
269
|
+
const projectNumber = constants_1.Constants.isDemoProject(projectId)
|
|
270
|
+
? constants_1.Constants.FAKE_PROJECT_NUMBER
|
|
271
|
+
: await (0, projectUtils_1.needProjectNumber)(options);
|
|
268
272
|
const aliases = (0, projectUtils_1.getAliases)(options, projectId);
|
|
269
273
|
const extensionEmulator = new extensionsEmulator_1.ExtensionsEmulator({
|
|
270
274
|
projectId,
|
|
@@ -274,7 +278,8 @@ async function startAll(options, showUI = true) {
|
|
|
274
278
|
extensions: options.config.get("extensions"),
|
|
275
279
|
});
|
|
276
280
|
const extensionsBackends = await extensionEmulator.getExtensionBackends();
|
|
277
|
-
|
|
281
|
+
const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
|
|
282
|
+
emulatableBackends.push(...filteredExtensionsBackends);
|
|
278
283
|
void track("Emulator Run", types_1.Emulators.EXTENSIONS);
|
|
279
284
|
registry_1.EmulatorRegistry.registerExtensionsEmulator();
|
|
280
285
|
}
|
|
@@ -302,6 +307,7 @@ async function startAll(options, showUI = true) {
|
|
|
302
307
|
host: functionsAddr.host,
|
|
303
308
|
port: functionsAddr.port,
|
|
304
309
|
debugPort: inspectFunctions,
|
|
310
|
+
projectAlias: options.projectAlias,
|
|
305
311
|
});
|
|
306
312
|
await startEmulator(functionsEmulator);
|
|
307
313
|
}
|
|
@@ -421,15 +427,11 @@ async function startAll(options, showUI = true) {
|
|
|
421
427
|
}
|
|
422
428
|
if (shouldStart(options, types_1.Emulators.STORAGE)) {
|
|
423
429
|
const storageAddr = await getAndCheckAddress(types_1.Emulators.STORAGE, options);
|
|
424
|
-
const storageConfig = options.config.data.storage;
|
|
425
|
-
if (!(storageConfig === null || storageConfig === void 0 ? void 0 : storageConfig.rules)) {
|
|
426
|
-
throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
|
|
427
|
-
}
|
|
428
430
|
const storageEmulator = new storage_1.StorageEmulator({
|
|
429
431
|
host: storageAddr.host,
|
|
430
432
|
port: storageAddr.port,
|
|
431
433
|
projectId: projectId,
|
|
432
|
-
rules:
|
|
434
|
+
rules: (0, config_1.getStorageRulesConfig)(projectId, options),
|
|
433
435
|
});
|
|
434
436
|
await startEmulator(storageEmulator);
|
|
435
437
|
if (exportMetadata.storage) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUnemulatedAPIs = void 0;
|
|
3
|
+
exports.checkForUnemulatedTriggerTypes = exports.getUnemulatedAPIs = void 0;
|
|
4
4
|
const planner = require("../../deploy/extensions/planner");
|
|
5
|
+
const controller_1 = require("../controller");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
5
7
|
const ensureApiEnabled_1 = require("../../ensureApiEnabled");
|
|
8
|
+
const functionsEmulatorShared_1 = require("../functionsEmulatorShared");
|
|
9
|
+
const types_1 = require("../types");
|
|
6
10
|
const EMULATED_APIS = [
|
|
7
11
|
"storage-component.googleapis.com",
|
|
8
12
|
"firestore.googleapis.com",
|
|
@@ -20,7 +24,8 @@ async function getUnemulatedAPIs(projectId, instances) {
|
|
|
20
24
|
unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
|
|
21
25
|
}
|
|
22
26
|
else {
|
|
23
|
-
const enabled =
|
|
27
|
+
const enabled = !constants_1.Constants.isDemoProject(projectId) &&
|
|
28
|
+
(await (0, ensureApiEnabled_1.check)(projectId, api.apiName, "extensions", true));
|
|
24
29
|
unemulatedAPIs[api.apiName] = {
|
|
25
30
|
apiName: api.apiName,
|
|
26
31
|
instanceIds: [i.instanceId],
|
|
@@ -33,3 +38,33 @@ async function getUnemulatedAPIs(projectId, instances) {
|
|
|
33
38
|
return Object.values(unemulatedAPIs);
|
|
34
39
|
}
|
|
35
40
|
exports.getUnemulatedAPIs = getUnemulatedAPIs;
|
|
41
|
+
function checkForUnemulatedTriggerTypes(backend, options) {
|
|
42
|
+
var _a;
|
|
43
|
+
const triggers = (_a = backend.predefinedTriggers) !== null && _a !== void 0 ? _a : [];
|
|
44
|
+
const unemulatedTriggers = triggers
|
|
45
|
+
.filter((definition) => {
|
|
46
|
+
if (definition.httpsTrigger) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (definition.eventTrigger) {
|
|
50
|
+
const service = (0, functionsEmulatorShared_1.getFunctionService)(definition);
|
|
51
|
+
switch (service) {
|
|
52
|
+
case constants_1.Constants.SERVICE_FIRESTORE:
|
|
53
|
+
return !(0, controller_1.shouldStart)(options, types_1.Emulators.FIRESTORE);
|
|
54
|
+
case constants_1.Constants.SERVICE_REALTIME_DATABASE:
|
|
55
|
+
return !(0, controller_1.shouldStart)(options, types_1.Emulators.DATABASE);
|
|
56
|
+
case constants_1.Constants.SERVICE_PUBSUB:
|
|
57
|
+
return !(0, controller_1.shouldStart)(options, types_1.Emulators.PUBSUB);
|
|
58
|
+
case constants_1.Constants.SERVICE_AUTH:
|
|
59
|
+
return !(0, controller_1.shouldStart)(options, types_1.Emulators.AUTH);
|
|
60
|
+
case constants_1.Constants.SERVICE_STORAGE:
|
|
61
|
+
return !(0, controller_1.shouldStart)(options, types_1.Emulators.STORAGE);
|
|
62
|
+
default:
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.map((definition) => constants_1.Constants.getServiceName((0, functionsEmulatorShared_1.getFunctionService)(definition)));
|
|
68
|
+
return [...new Set(unemulatedTriggers)];
|
|
69
|
+
}
|
|
70
|
+
exports.checkForUnemulatedTriggerTypes = checkForUnemulatedTriggerTypes;
|
|
@@ -17,10 +17,12 @@ const types_1 = require("./types");
|
|
|
17
17
|
const validation_1 = require("./extensions/validation");
|
|
18
18
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
19
19
|
const shortenUrl_1 = require("../shortenUrl");
|
|
20
|
+
const constants_1 = require("./constants");
|
|
20
21
|
class ExtensionsEmulator {
|
|
21
22
|
constructor(args) {
|
|
22
23
|
this.want = [];
|
|
23
24
|
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
|
|
25
|
+
this.pendingDownloads = new Map();
|
|
24
26
|
this.args = args;
|
|
25
27
|
}
|
|
26
28
|
async readManifest() {
|
|
@@ -31,7 +33,7 @@ class ExtensionsEmulator {
|
|
|
31
33
|
aliases: (_a = this.args.aliases) !== null && _a !== void 0 ? _a : [],
|
|
32
34
|
projectDir: this.args.projectDir,
|
|
33
35
|
extensions: this.args.extensions,
|
|
34
|
-
|
|
36
|
+
emulatorMode: true,
|
|
35
37
|
});
|
|
36
38
|
}
|
|
37
39
|
async ensureSourceCode(instance) {
|
|
@@ -42,13 +44,21 @@ class ExtensionsEmulator {
|
|
|
42
44
|
const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
|
|
43
45
|
path.join(os.homedir(), ".cache", "firebase", "extensions");
|
|
44
46
|
const sourceCodePath = path.join(cacheDir, ref);
|
|
47
|
+
if (this.pendingDownloads.get(ref)) {
|
|
48
|
+
await this.pendingDownloads.get(ref);
|
|
49
|
+
}
|
|
45
50
|
if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
const promise = this.downloadSource(instance, ref, sourceCodePath);
|
|
52
|
+
this.pendingDownloads.set(ref, promise);
|
|
53
|
+
await promise;
|
|
49
54
|
}
|
|
50
55
|
return sourceCodePath;
|
|
51
56
|
}
|
|
57
|
+
async downloadSource(instance, ref, sourceCodePath) {
|
|
58
|
+
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
59
|
+
await (0, download_1.downloadExtensionVersion)(ref, extensionVersion.sourceDownloadUri, sourceCodePath);
|
|
60
|
+
this.installAndBuildSourceCode(sourceCodePath);
|
|
61
|
+
}
|
|
52
62
|
hasValidSource(args) {
|
|
53
63
|
const requiredFiles = [
|
|
54
64
|
"./extension.yaml",
|
|
@@ -87,12 +97,13 @@ class ExtensionsEmulator {
|
|
|
87
97
|
const extensionDir = await this.ensureSourceCode(instance);
|
|
88
98
|
const functionsDir = path.join(extensionDir, "functions");
|
|
89
99
|
const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
|
|
90
|
-
const { extensionTriggers, nodeMajorVersion } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
|
|
100
|
+
const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
|
|
91
101
|
const extension = await planner.getExtension(instance);
|
|
92
102
|
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
93
103
|
return {
|
|
94
104
|
functionsDir,
|
|
95
|
-
env,
|
|
105
|
+
env: nonSecretEnv,
|
|
106
|
+
secretEnv: secretEnvVariables,
|
|
96
107
|
predefinedTriggers: extensionTriggers,
|
|
97
108
|
nodeMajorVersion: nodeMajorVersion,
|
|
98
109
|
extensionInstanceId: instance.instanceId,
|
|
@@ -131,10 +142,34 @@ class ExtensionsEmulator {
|
|
|
131
142
|
apiToWarn.enabled ? "" : clc.bold.underline(enablementUri),
|
|
132
143
|
]);
|
|
133
144
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
if (constants_1.Constants.isDemoProject(this.args.projectId)) {
|
|
146
|
+
this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
|
|
147
|
+
`${clc.bold(this.args.projectId)} is a demo project, so these Extensions may not work as expected.\n` +
|
|
148
|
+
table.toString());
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
|
|
152
|
+
`These calls will go to production Google Cloud APIs which may have real effects on ${clc.bold(this.args.projectId)}.\n` +
|
|
153
|
+
table.toString());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
filterUnemulatedTriggers(options, backends) {
|
|
158
|
+
let foundUnemulatedTrigger = false;
|
|
159
|
+
const filteredBackends = backends.filter((backend) => {
|
|
160
|
+
const unemulatedServices = (0, validation_1.checkForUnemulatedTriggerTypes)(backend, options);
|
|
161
|
+
if (unemulatedServices.length) {
|
|
162
|
+
foundUnemulatedTrigger = true;
|
|
163
|
+
const msg = ` ignored becuase it includes ${unemulatedServices.join(", ")} triggered functions, and the ${unemulatedServices.join(", ")} emulator does not exist or is not running.`;
|
|
164
|
+
this.logger.logLabeled("WARN", `extensions[${backend.extensionInstanceId}]`, msg);
|
|
165
|
+
}
|
|
166
|
+
return unemulatedServices.length === 0;
|
|
167
|
+
});
|
|
168
|
+
if (foundUnemulatedTrigger) {
|
|
169
|
+
const msg = "No Cloud Functions for these instances will be emulated, because partially emulating an Extension can lead to unexpected behavior. ";
|
|
170
|
+
this.logger.log("WARN", msg);
|
|
137
171
|
}
|
|
172
|
+
return filteredBackends;
|
|
138
173
|
}
|
|
139
174
|
}
|
|
140
175
|
exports.ExtensionsEmulator = ExtensionsEmulator;
|
|
@@ -79,7 +79,6 @@ class FunctionsEmulator {
|
|
|
79
79
|
createHubServer() {
|
|
80
80
|
this.workQueue.start();
|
|
81
81
|
const hub = express();
|
|
82
|
-
hub.use(cors({ origin: true }));
|
|
83
82
|
const dataMiddleware = (req, res, next) => {
|
|
84
83
|
const chunks = [];
|
|
85
84
|
req.on("data", (chunk) => {
|
|
@@ -158,7 +157,7 @@ class FunctionsEmulator {
|
|
|
158
157
|
const listBackendsHandler = (req, res) => {
|
|
159
158
|
res.json({ backends: this.getBackendInfo() });
|
|
160
159
|
};
|
|
161
|
-
hub.get(listBackendsRoute,
|
|
160
|
+
hub.get(listBackendsRoute, cors({ origin: true }), listBackendsHandler);
|
|
162
161
|
hub.post(backgroundFunctionRoute, dataMiddleware, backgroundHandler);
|
|
163
162
|
hub.post(multicastFunctionRoute, dataMiddleware, multicastHandler);
|
|
164
163
|
hub.all(httpsFunctionRoutes, dataMiddleware, httpsHandler);
|
|
@@ -253,7 +252,7 @@ class FunctionsEmulator {
|
|
|
253
252
|
}
|
|
254
253
|
let triggerDefinitions;
|
|
255
254
|
if (emulatableBackend.predefinedTriggers) {
|
|
256
|
-
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers);
|
|
255
|
+
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers, emulatableBackend.secretEnv);
|
|
257
256
|
}
|
|
258
257
|
else {
|
|
259
258
|
const runtimeConfig = this.getRuntimeConfig(emulatableBackend);
|
|
@@ -495,9 +494,13 @@ class FunctionsEmulator {
|
|
|
495
494
|
.map((t) => t.def);
|
|
496
495
|
return this.args.emulatableBackends.map((e) => {
|
|
497
496
|
var _a;
|
|
497
|
+
const envWithSecrets = Object.assign({}, e.env);
|
|
498
|
+
for (const s of e.secretEnv) {
|
|
499
|
+
envWithSecrets[s.key] = backend.secretVersionName(s);
|
|
500
|
+
}
|
|
498
501
|
return {
|
|
499
502
|
directory: e.functionsDir,
|
|
500
|
-
env:
|
|
503
|
+
env: envWithSecrets,
|
|
501
504
|
extensionInstanceId: e.extensionInstanceId,
|
|
502
505
|
extension: e.extension,
|
|
503
506
|
extensionVersion: e.extensionVersion,
|
|
@@ -570,6 +573,7 @@ class FunctionsEmulator {
|
|
|
570
573
|
const projectInfo = {
|
|
571
574
|
functionsSource: backend.functionsDir,
|
|
572
575
|
projectId: this.args.projectId,
|
|
576
|
+
projectAlias: this.args.projectAlias,
|
|
573
577
|
isEmulator: true,
|
|
574
578
|
};
|
|
575
579
|
if (functionsEnv.hasUserEnvs(projectInfo)) {
|
|
@@ -662,11 +666,12 @@ class FunctionsEmulator {
|
|
|
662
666
|
if (trigger) {
|
|
663
667
|
const secrets = trigger.secretEnvironmentVariables || [];
|
|
664
668
|
const accesses = secrets
|
|
665
|
-
.filter((s) => !secretEnvs[s.
|
|
669
|
+
.filter((s) => !secretEnvs[s.key])
|
|
666
670
|
.map(async (s) => {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
671
|
+
var _a;
|
|
672
|
+
this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`);
|
|
673
|
+
const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.secret, (_a = s.version) !== null && _a !== void 0 ? _a : "latest");
|
|
674
|
+
return [s.key, value];
|
|
670
675
|
});
|
|
671
676
|
const accessResults = await (0, utils_1.allSettled)(accesses);
|
|
672
677
|
const errs = [];
|