firebase-tools 13.15.4 → 13.16.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/dataconnect/client.js +2 -1
- package/lib/deploy/functions/prepare.js +0 -8
- package/lib/emulator/constants.js +7 -0
- package/lib/emulator/controller.js +12 -3
- package/lib/emulator/downloadableEmulators.js +3 -3
- package/lib/emulator/env.js +3 -0
- package/lib/emulator/functionsEmulator.js +19 -0
- package/lib/emulator/functionsEmulatorShared.js +17 -0
- package/lib/emulator/portUtils.js +1 -0
- package/lib/emulator/registry.js +1 -0
- package/lib/emulator/taskQueue.js +341 -0
- package/lib/emulator/tasksEmulator.js +238 -0
- package/lib/emulator/types.js +3 -0
- package/lib/init/features/dataconnect/index.js +4 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +12 -0
|
@@ -123,13 +123,14 @@ async function deleteConnector(name) {
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
exports.deleteConnector = deleteConnector;
|
|
126
|
-
async function listConnectors(serviceName) {
|
|
126
|
+
async function listConnectors(serviceName, fields = []) {
|
|
127
127
|
const connectors = [];
|
|
128
128
|
const getNextPage = async (pageToken = "") => {
|
|
129
129
|
const res = await dataconnectClient().get(`${serviceName}/connectors`, {
|
|
130
130
|
queryParams: {
|
|
131
131
|
pageSize: PAGE_SIZE_MAX,
|
|
132
132
|
pageToken,
|
|
133
|
+
fields: fields.join(","),
|
|
133
134
|
},
|
|
134
135
|
});
|
|
135
136
|
connectors.push(...(res.body.connectors || []));
|
|
@@ -27,7 +27,6 @@ const serviceusage_1 = require("../../gcp/serviceusage");
|
|
|
27
27
|
const applyHash_1 = require("./cache/applyHash");
|
|
28
28
|
const backend_1 = require("./backend");
|
|
29
29
|
const functional_1 = require("../../functional");
|
|
30
|
-
const prepare_1 = require("../extensions/prepare");
|
|
31
30
|
exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
|
|
32
31
|
async function prepare(context, options, payload) {
|
|
33
32
|
var _a, _b;
|
|
@@ -56,13 +55,6 @@ async function prepare(context, options, payload) {
|
|
|
56
55
|
}
|
|
57
56
|
context.codebaseDeployEvents = {};
|
|
58
57
|
const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
|
|
59
|
-
if (Object.values(wantBuilds).some((b) => b.extensions)) {
|
|
60
|
-
const extContext = {};
|
|
61
|
-
const extPayload = {};
|
|
62
|
-
await (0, prepare_1.prepareDynamicExtensions)(extContext, options, extPayload, wantBuilds);
|
|
63
|
-
context.extensions = extContext;
|
|
64
|
-
payload.extensions = extPayload;
|
|
65
|
-
}
|
|
66
58
|
const codebaseUsesEnvs = [];
|
|
67
59
|
const wantBackends = {};
|
|
68
60
|
for (const [codebase, wantBuild] of Object.entries(wantBuilds)) {
|
|
@@ -15,6 +15,7 @@ exports.DEFAULT_PORTS = {
|
|
|
15
15
|
storage: 9199,
|
|
16
16
|
eventarc: 9299,
|
|
17
17
|
dataconnect: 9399,
|
|
18
|
+
tasks: 9499,
|
|
18
19
|
};
|
|
19
20
|
exports.FIND_AVAILBLE_PORT_BY_DEFAULT = {
|
|
20
21
|
ui: true,
|
|
@@ -30,6 +31,7 @@ exports.FIND_AVAILBLE_PORT_BY_DEFAULT = {
|
|
|
30
31
|
extensions: false,
|
|
31
32
|
eventarc: true,
|
|
32
33
|
dataconnect: true,
|
|
34
|
+
tasks: true,
|
|
33
35
|
};
|
|
34
36
|
exports.EMULATOR_DESCRIPTION = {
|
|
35
37
|
ui: "Emulator UI",
|
|
@@ -45,6 +47,7 @@ exports.EMULATOR_DESCRIPTION = {
|
|
|
45
47
|
extensions: "Extensions Emulator",
|
|
46
48
|
eventarc: "Eventarc Emulator",
|
|
47
49
|
dataconnect: "Data Connect Emulator",
|
|
50
|
+
tasks: "Cloud Tasks Emulator",
|
|
48
51
|
};
|
|
49
52
|
exports.DEFAULT_HOST = "localhost";
|
|
50
53
|
class Constants {
|
|
@@ -70,6 +73,8 @@ class Constants {
|
|
|
70
73
|
return "test lab";
|
|
71
74
|
case this.SERVICE_EVENTARC:
|
|
72
75
|
return "eventarc";
|
|
76
|
+
case this.SERVICE_CLOUD_TASKS:
|
|
77
|
+
return "tasks";
|
|
73
78
|
default:
|
|
74
79
|
return service;
|
|
75
80
|
}
|
|
@@ -100,12 +105,14 @@ Constants.FIREBASE_STORAGE_EMULATOR_HOST = "FIREBASE_STORAGE_EMULATOR_HOST";
|
|
|
100
105
|
Constants.CLOUD_STORAGE_EMULATOR_HOST = "STORAGE_EMULATOR_HOST";
|
|
101
106
|
Constants.PUBSUB_EMULATOR_HOST = "PUBSUB_EMULATOR_HOST";
|
|
102
107
|
Constants.CLOUD_EVENTARC_EMULATOR_HOST = "CLOUD_EVENTARC_EMULATOR_HOST";
|
|
108
|
+
Constants.CLOUD_TASKS_EMULATOR_HOST = "CLOUD_TASKS_EMULATOR_HOST";
|
|
103
109
|
Constants.FIREBASE_EMULATOR_HUB = "FIREBASE_EMULATOR_HUB";
|
|
104
110
|
Constants.FIREBASE_GA_SESSION = "FIREBASE_GA_SESSION";
|
|
105
111
|
Constants.SERVICE_FIRESTORE = "firestore.googleapis.com";
|
|
106
112
|
Constants.SERVICE_REALTIME_DATABASE = "firebaseio.com";
|
|
107
113
|
Constants.SERVICE_PUBSUB = "pubsub.googleapis.com";
|
|
108
114
|
Constants.SERVICE_EVENTARC = "eventarc.googleapis.com";
|
|
115
|
+
Constants.SERVICE_CLOUD_TASKS = "cloudtasks.googleapis.com";
|
|
109
116
|
Constants.SERVICE_FIREALERTS = "firebasealerts.googleapis.com";
|
|
110
117
|
Constants.SERVICE_ANALYTICS = "app-measurement.com";
|
|
111
118
|
Constants.SERVICE_AUTH = "firebaseauth.googleapis.com";
|
|
@@ -44,6 +44,7 @@ const hostingEmulator_1 = require("./hostingEmulator");
|
|
|
44
44
|
const pubsubEmulator_1 = require("./pubsubEmulator");
|
|
45
45
|
const storage_1 = require("./storage");
|
|
46
46
|
const fileUtils_1 = require("../dataconnect/fileUtils");
|
|
47
|
+
const tasksEmulator_1 = require("./tasksEmulator");
|
|
47
48
|
const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
|
|
48
49
|
async function exportOnExit(options) {
|
|
49
50
|
const exportOnExitDir = options.exportOnExit;
|
|
@@ -159,7 +160,7 @@ function findExportMetadata(importPath) {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
async function startAll(options, showUI = true, runningTestScript = false) {
|
|
162
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
163
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
163
164
|
const targets = filterEmulatorTargets(options);
|
|
164
165
|
options.targets = targets;
|
|
165
166
|
const singleProjectModeEnabled = ((_a = options.config.src.emulators) === null || _a === void 0 ? void 0 : _a.singleProjectMode) === undefined ||
|
|
@@ -224,10 +225,12 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
224
225
|
if (emulatableBackends.length) {
|
|
225
226
|
listenConfig[types_1.Emulators.FUNCTIONS] = getListenConfig(options, types_1.Emulators.FUNCTIONS);
|
|
226
227
|
listenConfig[types_1.Emulators.EVENTARC] = getListenConfig(options, types_1.Emulators.EVENTARC);
|
|
228
|
+
listenConfig[types_1.Emulators.TASKS] = getListenConfig(options, types_1.Emulators.TASKS);
|
|
227
229
|
}
|
|
228
230
|
for (const emulator of types_1.ALL_EMULATORS) {
|
|
229
231
|
if (emulator === types_1.Emulators.FUNCTIONS ||
|
|
230
232
|
emulator === types_1.Emulators.EVENTARC ||
|
|
233
|
+
emulator === types_1.Emulators.TASKS ||
|
|
231
234
|
emulator === types_1.Emulators.EXTENSIONS ||
|
|
232
235
|
(emulator === types_1.Emulators.UI && !showUI)) {
|
|
233
236
|
continue;
|
|
@@ -330,8 +333,8 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
330
333
|
await startEmulator(extensionEmulator);
|
|
331
334
|
}
|
|
332
335
|
if (emulatableBackends.length) {
|
|
333
|
-
if (!listenForEmulator.functions || !listenForEmulator.eventarc) {
|
|
334
|
-
listenForEmulator = await (0, portUtils_1.resolveHostAndAssignPorts)(Object.assign(Object.assign({}, listenForEmulator), { functions: (_f = listenForEmulator.functions) !== null && _f !== void 0 ? _f : getListenConfig(options, types_1.Emulators.FUNCTIONS), eventarc: (_g = listenForEmulator.eventarc) !== null && _g !== void 0 ? _g : getListenConfig(options, types_1.Emulators.EVENTARC) }));
|
|
336
|
+
if (!listenForEmulator.functions || !listenForEmulator.eventarc || !listenForEmulator.tasks) {
|
|
337
|
+
listenForEmulator = await (0, portUtils_1.resolveHostAndAssignPorts)(Object.assign(Object.assign({}, listenForEmulator), { functions: (_f = listenForEmulator.functions) !== null && _f !== void 0 ? _f : getListenConfig(options, types_1.Emulators.FUNCTIONS), eventarc: (_g = listenForEmulator.eventarc) !== null && _g !== void 0 ? _g : getListenConfig(options, types_1.Emulators.EVENTARC), tasks: (_h = listenForEmulator.eventarc) !== null && _h !== void 0 ? _h : getListenConfig(options, types_1.Emulators.TASKS) }));
|
|
335
338
|
hubLogger.log("DEBUG", "late-assigned ports for functions and eventarc emulators", {
|
|
336
339
|
user: listenForEmulator,
|
|
337
340
|
});
|
|
@@ -368,6 +371,12 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
368
371
|
port: eventarcAddr.port,
|
|
369
372
|
});
|
|
370
373
|
await startEmulator(eventarcEmulator);
|
|
374
|
+
const tasksAddr = legacyGetFirstAddr(types_1.Emulators.TASKS);
|
|
375
|
+
const tasksEmulator = new tasksEmulator_1.TasksEmulator({
|
|
376
|
+
host: tasksAddr.host,
|
|
377
|
+
port: tasksAddr.port,
|
|
378
|
+
});
|
|
379
|
+
await startEmulator(tasksEmulator);
|
|
371
380
|
}
|
|
372
381
|
if (listenForEmulator.firestore) {
|
|
373
382
|
const firestoreLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FIRESTORE);
|
|
@@ -23,9 +23,9 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
23
23
|
expectedChecksum: "2fd771101c0e1f7898c04c9204f2ce63",
|
|
24
24
|
},
|
|
25
25
|
firestore: {
|
|
26
|
-
version: "1.19.
|
|
27
|
-
expectedSize:
|
|
28
|
-
expectedChecksum: "
|
|
26
|
+
version: "1.19.8",
|
|
27
|
+
expectedSize: 63634791,
|
|
28
|
+
expectedChecksum: "9b43a6daa590678de9b7df6d68260395",
|
|
29
29
|
},
|
|
30
30
|
storage: {
|
|
31
31
|
version: "1.1.3",
|
package/lib/emulator/env.js
CHANGED
|
@@ -31,6 +31,9 @@ function setEnvVarsForEmulators(env, emulators) {
|
|
|
31
31
|
case types_1.Emulators.EVENTARC:
|
|
32
32
|
env[constants_1.Constants.CLOUD_EVENTARC_EMULATOR_HOST] = `http://${host}`;
|
|
33
33
|
break;
|
|
34
|
+
case types_1.Emulators.TASKS:
|
|
35
|
+
env[constants_1.Constants.CLOUD_TASKS_EMULATOR_HOST] = host;
|
|
36
|
+
break;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -386,6 +386,9 @@ class FunctionsEmulator {
|
|
|
386
386
|
if (definition.httpsTrigger) {
|
|
387
387
|
added = true;
|
|
388
388
|
url = FunctionsEmulator.getHttpFunctionUrl(this.args.projectId, definition.name, definition.region);
|
|
389
|
+
if (definition.taskQueueTrigger) {
|
|
390
|
+
added = await this.addTaskQueueTrigger(this.args.projectId, definition.region, definition.name, url, definition.taskQueueTrigger);
|
|
391
|
+
}
|
|
389
392
|
}
|
|
390
393
|
else if (definition.eventTrigger) {
|
|
391
394
|
const service = (0, functionsEmulatorShared_1.getFunctionService)(definition);
|
|
@@ -753,6 +756,22 @@ class FunctionsEmulator {
|
|
|
753
756
|
};
|
|
754
757
|
return true;
|
|
755
758
|
}
|
|
759
|
+
async addTaskQueueTrigger(projectId, location, entryPoint, defaultUri, taskQueueTrigger) {
|
|
760
|
+
logger_1.logger.debug(`addTaskQueueTrigger`, JSON.stringify(taskQueueTrigger));
|
|
761
|
+
if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.TASKS)) {
|
|
762
|
+
logger_1.logger.debug(`addTaskQueueTrigger`, "TQ not running");
|
|
763
|
+
return Promise.resolve(false);
|
|
764
|
+
}
|
|
765
|
+
const bundle = Object.assign(Object.assign({}, taskQueueTrigger), { defaultUri });
|
|
766
|
+
try {
|
|
767
|
+
await registry_1.EmulatorRegistry.client(types_1.Emulators.TASKS).post(`/projects/${projectId}/locations/${location}/queues/${entryPoint}`, bundle);
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
this.logger.log("WARN", "Error adding Task Queue function: " + err);
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
756
775
|
getProjectId() {
|
|
757
776
|
return this.args.projectId;
|
|
758
777
|
}
|
|
@@ -61,6 +61,7 @@ function prepareEndpoints(endpoints) {
|
|
|
61
61
|
}
|
|
62
62
|
exports.prepareEndpoints = prepareEndpoints;
|
|
63
63
|
function emulatedFunctionsFromEndpoints(endpoints) {
|
|
64
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
64
65
|
const regionDefinitions = [];
|
|
65
66
|
for (const endpoint of endpoints) {
|
|
66
67
|
if (!endpoint.region) {
|
|
@@ -131,6 +132,19 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
131
132
|
}
|
|
132
133
|
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
133
134
|
def.httpsTrigger = {};
|
|
135
|
+
def.taskQueueTrigger = {
|
|
136
|
+
retryConfig: {
|
|
137
|
+
maxAttempts: (_a = endpoint.taskQueueTrigger.retryConfig) === null || _a === void 0 ? void 0 : _a.maxAttempts,
|
|
138
|
+
maxRetrySeconds: (_b = endpoint.taskQueueTrigger.retryConfig) === null || _b === void 0 ? void 0 : _b.maxRetrySeconds,
|
|
139
|
+
maxBackoffSeconds: (_c = endpoint.taskQueueTrigger.retryConfig) === null || _c === void 0 ? void 0 : _c.maxBackoffSeconds,
|
|
140
|
+
maxDoublings: (_d = endpoint.taskQueueTrigger.retryConfig) === null || _d === void 0 ? void 0 : _d.maxDoublings,
|
|
141
|
+
minBackoffSeconds: (_e = endpoint.taskQueueTrigger.retryConfig) === null || _e === void 0 ? void 0 : _e.minBackoffSeconds,
|
|
142
|
+
},
|
|
143
|
+
rateLimits: {
|
|
144
|
+
maxConcurrentDispatches: (_f = endpoint.taskQueueTrigger.rateLimits) === null || _f === void 0 ? void 0 : _f.maxConcurrentDispatches,
|
|
145
|
+
maxDispatchesPerSecond: (_g = endpoint.taskQueueTrigger.rateLimits) === null || _g === void 0 ? void 0 : _g.maxDispatchesPerSecond,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
134
148
|
}
|
|
135
149
|
else {
|
|
136
150
|
}
|
|
@@ -189,6 +203,9 @@ function getFunctionService(def) {
|
|
|
189
203
|
if (def.httpsTrigger) {
|
|
190
204
|
return "https";
|
|
191
205
|
}
|
|
206
|
+
if (def.taskQueueTrigger) {
|
|
207
|
+
return constants_1.Constants.SERVICE_CLOUD_TASKS;
|
|
208
|
+
}
|
|
192
209
|
return "unknown";
|
|
193
210
|
}
|
|
194
211
|
exports.getFunctionService = getFunctionService;
|
package/lib/emulator/registry.js
CHANGED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskQueue = exports.TaskStatus = exports.Queue = void 0;
|
|
4
|
+
const abort_controller_1 = require("abort-controller");
|
|
5
|
+
const emulatorLogger_1 = require("./emulatorLogger");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const node_fetch_1 = require("node-fetch");
|
|
8
|
+
class Node {
|
|
9
|
+
constructor(data) {
|
|
10
|
+
this.data = data;
|
|
11
|
+
this.next = null;
|
|
12
|
+
this.prev = null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
class Queue {
|
|
16
|
+
constructor(capacity = 10000) {
|
|
17
|
+
this.nodeMap = {};
|
|
18
|
+
this.count = 0;
|
|
19
|
+
this.first = null;
|
|
20
|
+
this.last = null;
|
|
21
|
+
this.capacity = capacity;
|
|
22
|
+
}
|
|
23
|
+
enqueue(id, item) {
|
|
24
|
+
if (this.count >= this.capacity) {
|
|
25
|
+
throw new Error("Queue has reached capacity");
|
|
26
|
+
}
|
|
27
|
+
const newNode = new Node(item);
|
|
28
|
+
if (this.nodeMap[id] !== undefined) {
|
|
29
|
+
throw new Error("Queue IDs must be unique");
|
|
30
|
+
}
|
|
31
|
+
this.nodeMap[id] = newNode;
|
|
32
|
+
if (!this.first) {
|
|
33
|
+
this.first = newNode;
|
|
34
|
+
}
|
|
35
|
+
if (this.last) {
|
|
36
|
+
this.last.next = newNode;
|
|
37
|
+
}
|
|
38
|
+
newNode.prev = this.last;
|
|
39
|
+
this.last = newNode;
|
|
40
|
+
this.count++;
|
|
41
|
+
}
|
|
42
|
+
peek() {
|
|
43
|
+
if (this.first) {
|
|
44
|
+
return this.first.data;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error("Trying to peek into an empty queue");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
dequeue() {
|
|
51
|
+
if (this.first) {
|
|
52
|
+
const currentFirst = this.first;
|
|
53
|
+
this.first = this.first.next;
|
|
54
|
+
if (this.last === currentFirst) {
|
|
55
|
+
this.last = null;
|
|
56
|
+
}
|
|
57
|
+
this.count--;
|
|
58
|
+
return currentFirst.data;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error("Trying to dequeue from an empty queue");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
remove(id) {
|
|
65
|
+
if (this.nodeMap[id] === undefined) {
|
|
66
|
+
throw new Error("Trying to remove a task that doesn't exist");
|
|
67
|
+
}
|
|
68
|
+
const toRemove = this.nodeMap[id];
|
|
69
|
+
if (toRemove.next === null && toRemove.prev === null) {
|
|
70
|
+
this.first = null;
|
|
71
|
+
this.last = null;
|
|
72
|
+
}
|
|
73
|
+
else if (toRemove.next === null) {
|
|
74
|
+
this.last = toRemove.prev;
|
|
75
|
+
toRemove.prev.next = null;
|
|
76
|
+
}
|
|
77
|
+
else if (toRemove.prev === null) {
|
|
78
|
+
this.first = toRemove.next;
|
|
79
|
+
toRemove.next.prev = null;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const prev = toRemove.prev;
|
|
83
|
+
const next = toRemove.next;
|
|
84
|
+
prev.next = next;
|
|
85
|
+
next.prev = prev;
|
|
86
|
+
}
|
|
87
|
+
delete this.nodeMap[id];
|
|
88
|
+
this.count--;
|
|
89
|
+
}
|
|
90
|
+
getAll() {
|
|
91
|
+
const all = [];
|
|
92
|
+
let curr = this.first;
|
|
93
|
+
while (curr) {
|
|
94
|
+
all.push(curr.data);
|
|
95
|
+
curr = curr.next;
|
|
96
|
+
}
|
|
97
|
+
return all;
|
|
98
|
+
}
|
|
99
|
+
isEmpty() {
|
|
100
|
+
return this.first === null;
|
|
101
|
+
}
|
|
102
|
+
size() {
|
|
103
|
+
return this.count;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.Queue = Queue;
|
|
107
|
+
var TaskStatus;
|
|
108
|
+
(function (TaskStatus) {
|
|
109
|
+
TaskStatus[TaskStatus["NOT_STARTED"] = 0] = "NOT_STARTED";
|
|
110
|
+
TaskStatus[TaskStatus["RUNNING"] = 1] = "RUNNING";
|
|
111
|
+
TaskStatus[TaskStatus["RETRY"] = 2] = "RETRY";
|
|
112
|
+
TaskStatus[TaskStatus["FAILED"] = 3] = "FAILED";
|
|
113
|
+
TaskStatus[TaskStatus["FINISHED"] = 4] = "FINISHED";
|
|
114
|
+
})(TaskStatus = exports.TaskStatus || (exports.TaskStatus = {}));
|
|
115
|
+
class TaskQueue {
|
|
116
|
+
constructor(key, config) {
|
|
117
|
+
this.key = key;
|
|
118
|
+
this.config = config;
|
|
119
|
+
this.queue = new Queue();
|
|
120
|
+
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.TASKS);
|
|
121
|
+
this.tokens = 0;
|
|
122
|
+
this.addedTimes = [];
|
|
123
|
+
this.completedTimes = [];
|
|
124
|
+
this.failedTimes = [];
|
|
125
|
+
this.maxTokens = Math.max(this.config.rateLimits.maxDispatchesPerSecond, 1.1);
|
|
126
|
+
this.lastTokenUpdate = Date.now();
|
|
127
|
+
this.queuedIds = new Set();
|
|
128
|
+
this.dispatches = new Array(this.config.rateLimits.maxConcurrentDispatches).fill(null);
|
|
129
|
+
this.openDispatches = Array.from(this.dispatches.keys());
|
|
130
|
+
}
|
|
131
|
+
dispatchTasks() {
|
|
132
|
+
while (!this.queue.isEmpty() && this.openDispatches.length > 0 && this.tokens >= 1) {
|
|
133
|
+
const dispatchLocation = this.openDispatches.pop();
|
|
134
|
+
if (dispatchLocation !== undefined) {
|
|
135
|
+
const dispatch = this.queue.dequeue();
|
|
136
|
+
dispatch.metadata.lastRunTime = null;
|
|
137
|
+
dispatch.metadata.currentAttempt = 1;
|
|
138
|
+
dispatch.metadata.status = TaskStatus.NOT_STARTED;
|
|
139
|
+
dispatch.metadata.startTime = Date.now();
|
|
140
|
+
this.dispatches[dispatchLocation] = dispatch;
|
|
141
|
+
this.tokens--;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
setDispatch(dispatches) {
|
|
146
|
+
this.dispatches = dispatches;
|
|
147
|
+
const open = [];
|
|
148
|
+
for (let i = 0; i < this.dispatches.length; i++) {
|
|
149
|
+
if (dispatches[i] === null) {
|
|
150
|
+
open.push(i);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.openDispatches = open;
|
|
154
|
+
}
|
|
155
|
+
getDispatch() {
|
|
156
|
+
return this.dispatches;
|
|
157
|
+
}
|
|
158
|
+
processDispatch() {
|
|
159
|
+
var _a;
|
|
160
|
+
for (let i = 0; i < this.dispatches.length; i++) {
|
|
161
|
+
if (this.dispatches[i] !== null) {
|
|
162
|
+
switch ((_a = this.dispatches[i]) === null || _a === void 0 ? void 0 : _a.metadata.status) {
|
|
163
|
+
case TaskStatus.FAILED:
|
|
164
|
+
this.dispatches[i] = null;
|
|
165
|
+
this.openDispatches.push(i);
|
|
166
|
+
this.completedTimes.push(Date.now());
|
|
167
|
+
this.failedTimes.push(Date.now());
|
|
168
|
+
break;
|
|
169
|
+
case TaskStatus.NOT_STARTED:
|
|
170
|
+
void this.runTask(i);
|
|
171
|
+
break;
|
|
172
|
+
case TaskStatus.RETRY:
|
|
173
|
+
this.handleRetry(i);
|
|
174
|
+
break;
|
|
175
|
+
case TaskStatus.FINISHED:
|
|
176
|
+
this.dispatches[i] = null;
|
|
177
|
+
this.openDispatches.push(i);
|
|
178
|
+
this.completedTimes.push(Date.now());
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async runTask(dispatchIndex) {
|
|
185
|
+
if (this.dispatches[dispatchIndex] === null) {
|
|
186
|
+
throw new Error("Trying to dispatch a nonexistent task");
|
|
187
|
+
}
|
|
188
|
+
const emulatedTask = this.dispatches[dispatchIndex];
|
|
189
|
+
if (emulatedTask.metadata.lastRunTime !== null &&
|
|
190
|
+
Date.now() - emulatedTask.metadata.lastRunTime < emulatedTask.metadata.currentBackoff * 1000) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
emulatedTask.metadata.status = TaskStatus.RUNNING;
|
|
194
|
+
try {
|
|
195
|
+
const headers = Object.assign({ "Content-Type": "application/json", "X-CloudTasks-QueueName": this.key, "X-CloudTasks-TaskName": emulatedTask.task.name.split("/").pop(), "X-CloudTasks-TaskRetryCount": `${emulatedTask.metadata.currentAttempt - 1}`, "X-CloudTasks-TaskExecutionCount": `${emulatedTask.metadata.executionCount}`, "X-CloudTasks-TaskETA": `${emulatedTask.task.scheduleTime || Date.now()}` }, emulatedTask.task.httpRequest.headers);
|
|
196
|
+
if (emulatedTask.metadata.previousResponse) {
|
|
197
|
+
headers["X-CloudTasks-TaskPreviousResponse"] = `${emulatedTask.metadata.previousResponse}`;
|
|
198
|
+
}
|
|
199
|
+
const controller = new abort_controller_1.default();
|
|
200
|
+
const signal = controller.signal;
|
|
201
|
+
const request = (0, node_fetch_1.default)(emulatedTask.task.httpRequest.url, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: headers,
|
|
204
|
+
body: JSON.stringify(emulatedTask.task.httpRequest.body),
|
|
205
|
+
signal: signal,
|
|
206
|
+
});
|
|
207
|
+
const dispatchDeadline = emulatedTask.task.dispatchDeadline;
|
|
208
|
+
const dispatchDeadlineSeconds = dispatchDeadline
|
|
209
|
+
? parseInt(dispatchDeadline.substring(0, dispatchDeadline.length - 1))
|
|
210
|
+
: 60;
|
|
211
|
+
const abortId = setTimeout(() => {
|
|
212
|
+
controller.abort();
|
|
213
|
+
}, dispatchDeadlineSeconds * 1000);
|
|
214
|
+
const response = await request;
|
|
215
|
+
clearTimeout(abortId);
|
|
216
|
+
if (response.ok) {
|
|
217
|
+
emulatedTask.metadata.status = TaskStatus.FINISHED;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (!(response.status >= 500 && response.status <= 599)) {
|
|
222
|
+
emulatedTask.metadata.executionCount++;
|
|
223
|
+
}
|
|
224
|
+
emulatedTask.metadata.previousResponse = response.status;
|
|
225
|
+
emulatedTask.metadata.status = TaskStatus.RETRY;
|
|
226
|
+
emulatedTask.metadata.lastRunTime = Date.now();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
this.logger.logLabeled("WARN", `${e}`);
|
|
231
|
+
emulatedTask.metadata.status = TaskStatus.RETRY;
|
|
232
|
+
emulatedTask.metadata.lastRunTime = Date.now();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
handleRetry(dispatchIndex) {
|
|
236
|
+
if (this.dispatches[dispatchIndex] === null) {
|
|
237
|
+
throw new Error("Trying to retry a nonexistent task");
|
|
238
|
+
}
|
|
239
|
+
const { metadata } = this.dispatches[dispatchIndex];
|
|
240
|
+
const { retryConfig } = this.config;
|
|
241
|
+
if (this.shouldStopRetrying(metadata, retryConfig)) {
|
|
242
|
+
metadata.status = TaskStatus.FAILED;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this.updateMetadata(metadata, retryConfig);
|
|
246
|
+
metadata.status = TaskStatus.NOT_STARTED;
|
|
247
|
+
}
|
|
248
|
+
shouldStopRetrying(metadata, retryOptions) {
|
|
249
|
+
if (metadata.currentAttempt > retryOptions.maxAttempts) {
|
|
250
|
+
if (retryOptions.maxRetrySeconds === null || retryOptions.maxRetrySeconds === 0) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (Date.now() - metadata.startTime > retryOptions.maxRetrySeconds * 1000) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
updateMetadata(metadata, retryOptions) {
|
|
260
|
+
const timeMultplier = Math.pow(2, Math.min(metadata.currentAttempt - 1, retryOptions.maxDoublings)) +
|
|
261
|
+
Math.max(0, metadata.currentAttempt - retryOptions.maxDoublings - 1) *
|
|
262
|
+
Math.pow(2, retryOptions.maxDoublings);
|
|
263
|
+
metadata.currentBackoff = Math.min(retryOptions.maxBackoffSeconds, timeMultplier * retryOptions.minBackoffSeconds);
|
|
264
|
+
metadata.currentAttempt++;
|
|
265
|
+
}
|
|
266
|
+
isActive() {
|
|
267
|
+
return !this.queue.isEmpty() || this.dispatches.some((e) => e !== null);
|
|
268
|
+
}
|
|
269
|
+
refillTokens() {
|
|
270
|
+
const tokensToAdd = ((Date.now() - this.lastTokenUpdate) / 1000) * this.config.rateLimits.maxDispatchesPerSecond;
|
|
271
|
+
this.addTokens(tokensToAdd);
|
|
272
|
+
this.lastTokenUpdate = Date.now();
|
|
273
|
+
}
|
|
274
|
+
addTokens(t) {
|
|
275
|
+
this.tokens += t;
|
|
276
|
+
this.tokens = Math.min(this.tokens, this.maxTokens);
|
|
277
|
+
}
|
|
278
|
+
setTokens(t) {
|
|
279
|
+
this.tokens = t;
|
|
280
|
+
}
|
|
281
|
+
getTokens() {
|
|
282
|
+
return this.tokens;
|
|
283
|
+
}
|
|
284
|
+
enqueue(task) {
|
|
285
|
+
if (this.queuedIds.has(task.name)) {
|
|
286
|
+
throw new Error(`A task has already been queued with id ${task.name}`);
|
|
287
|
+
}
|
|
288
|
+
const emulatedTask = {
|
|
289
|
+
task: task,
|
|
290
|
+
metadata: {
|
|
291
|
+
currentAttempt: 0,
|
|
292
|
+
currentBackoff: 0,
|
|
293
|
+
startTime: 0,
|
|
294
|
+
status: TaskStatus.NOT_STARTED,
|
|
295
|
+
lastRunTime: null,
|
|
296
|
+
previousResponse: null,
|
|
297
|
+
executionCount: 0,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
emulatedTask.task.httpRequest.url =
|
|
301
|
+
emulatedTask.task.httpRequest.url === ""
|
|
302
|
+
? this.config.defaultUri
|
|
303
|
+
: emulatedTask.task.httpRequest.url;
|
|
304
|
+
this.queue.enqueue(emulatedTask.task.name, emulatedTask);
|
|
305
|
+
this.queuedIds.add(task.name);
|
|
306
|
+
this.addedTimes.push(Date.now());
|
|
307
|
+
}
|
|
308
|
+
delete(taskId) {
|
|
309
|
+
this.queue.remove(taskId);
|
|
310
|
+
}
|
|
311
|
+
getDebugInfo() {
|
|
312
|
+
return `
|
|
313
|
+
Task Queue (${this.key}):
|
|
314
|
+
- Active: ${this.isActive().toString()}
|
|
315
|
+
- Tokens: ${this.tokens}
|
|
316
|
+
- In Queue: ${this.queue.size()}
|
|
317
|
+
- Dispatch: [
|
|
318
|
+
${this.dispatches.map((t) => (t === null ? "empty" : t.task.name)).join(",\n")}
|
|
319
|
+
]
|
|
320
|
+
- Open Locations: [${this.openDispatches.join(", ")}]
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
getStatistics() {
|
|
324
|
+
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
|
|
325
|
+
const oneMinuteAgo = Date.now() - 60 * 1000;
|
|
326
|
+
this.addedTimes = this.addedTimes.filter((t) => t > fiveMinutesAgo);
|
|
327
|
+
this.failedTimes = this.failedTimes.filter((t) => t > fiveMinutesAgo);
|
|
328
|
+
this.completedTimes = this.completedTimes.filter((t) => t > oneMinuteAgo);
|
|
329
|
+
return {
|
|
330
|
+
numberOfTasks: this.queue.size(),
|
|
331
|
+
tasksAdded: this.addedTimes.length / 5,
|
|
332
|
+
completedLastMin: this.completedTimes.length,
|
|
333
|
+
failedTasks: this.failedTimes.length / 5,
|
|
334
|
+
runningTasks: this.dispatches.length,
|
|
335
|
+
maxRate: this.config.rateLimits.maxDispatchesPerSecond,
|
|
336
|
+
maxConcurrent: this.config.rateLimits.maxConcurrentDispatches,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
exports.TaskQueue = TaskQueue;
|
|
341
|
+
TaskQueue.TASK_QUEUE_INTERVAL = 1000;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TasksEmulator = exports.TaskQueueController = void 0;
|
|
4
|
+
const express = require("express");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const emulatorLogger_1 = require("./emulatorLogger");
|
|
9
|
+
const taskQueue_1 = require("./taskQueue");
|
|
10
|
+
const cors = require("cors");
|
|
11
|
+
const RETRY_CONFIG_DEFAULTS = {
|
|
12
|
+
maxAttempts: 3,
|
|
13
|
+
maxRetrySeconds: null,
|
|
14
|
+
maxBackoffSeconds: 60 * 60,
|
|
15
|
+
maxDoublings: 16,
|
|
16
|
+
minBackoffSeconds: 0.1,
|
|
17
|
+
};
|
|
18
|
+
const RATE_LIMITS_DEFAULT = {
|
|
19
|
+
maxConcurrentDispatches: 1000,
|
|
20
|
+
maxDispatchesPerSecond: 500,
|
|
21
|
+
};
|
|
22
|
+
class TaskQueueController {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.queues = {};
|
|
25
|
+
this.tokenRefillIds = [];
|
|
26
|
+
this.running = false;
|
|
27
|
+
this.listenId = null;
|
|
28
|
+
}
|
|
29
|
+
enqueue(key, task) {
|
|
30
|
+
if (!this.queues[key]) {
|
|
31
|
+
throw new Error("Queue does not exist");
|
|
32
|
+
}
|
|
33
|
+
this.queues[key].enqueue(task);
|
|
34
|
+
}
|
|
35
|
+
delete(key, taskId) {
|
|
36
|
+
if (!this.queues[key]) {
|
|
37
|
+
throw new Error("Queue does not exist");
|
|
38
|
+
}
|
|
39
|
+
this.queues[key].delete(taskId);
|
|
40
|
+
}
|
|
41
|
+
createQueue(key, config) {
|
|
42
|
+
const newQueue = new taskQueue_1.TaskQueue(key, config);
|
|
43
|
+
const intervalID = setInterval(() => newQueue.refillTokens(), TaskQueueController.TOKEN_REFRESH_INTERVAL);
|
|
44
|
+
this.tokenRefillIds.push(intervalID);
|
|
45
|
+
this.queues[key] = newQueue;
|
|
46
|
+
}
|
|
47
|
+
listen() {
|
|
48
|
+
let shouldUpdate = false;
|
|
49
|
+
for (const [_key, queue] of Object.entries(this.queues)) {
|
|
50
|
+
shouldUpdate = shouldUpdate || queue.isActive();
|
|
51
|
+
}
|
|
52
|
+
if (shouldUpdate) {
|
|
53
|
+
this.updateQueues();
|
|
54
|
+
this.listenId = setTimeout(() => this.listen(), TaskQueueController.UPDATE_TIMEOUT);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.listenId = setTimeout(() => this.listen(), TaskQueueController.LISTEN_TIMEOUT);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
updateQueues() {
|
|
61
|
+
for (const [_key, queue] of Object.entries(this.queues)) {
|
|
62
|
+
if (queue.isActive()) {
|
|
63
|
+
queue.dispatchTasks();
|
|
64
|
+
queue.processDispatch();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
start() {
|
|
69
|
+
this.running = true;
|
|
70
|
+
this.listen();
|
|
71
|
+
}
|
|
72
|
+
stop() {
|
|
73
|
+
if (this.listenId) {
|
|
74
|
+
clearTimeout(this.listenId);
|
|
75
|
+
}
|
|
76
|
+
this.tokenRefillIds.forEach(clearInterval);
|
|
77
|
+
this.running = false;
|
|
78
|
+
}
|
|
79
|
+
isRunning() {
|
|
80
|
+
return this.running;
|
|
81
|
+
}
|
|
82
|
+
getStatistics() {
|
|
83
|
+
const stats = {};
|
|
84
|
+
for (const [key, queue] of Object.entries(this.queues)) {
|
|
85
|
+
stats[key] = queue.getStatistics();
|
|
86
|
+
}
|
|
87
|
+
return stats;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.TaskQueueController = TaskQueueController;
|
|
91
|
+
TaskQueueController.UPDATE_TIMEOUT = 0;
|
|
92
|
+
TaskQueueController.LISTEN_TIMEOUT = 1000;
|
|
93
|
+
TaskQueueController.TOKEN_REFRESH_INTERVAL = 1000;
|
|
94
|
+
class TasksEmulator {
|
|
95
|
+
constructor(args) {
|
|
96
|
+
this.args = args;
|
|
97
|
+
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.TASKS);
|
|
98
|
+
this.controller = new TaskQueueController();
|
|
99
|
+
}
|
|
100
|
+
validateQueueId(queueId) {
|
|
101
|
+
if (typeof queueId !== "string") {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (queueId.length > 100) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const regex = /^[A-Za-z0-9-]+$/;
|
|
108
|
+
return regex.test(queueId);
|
|
109
|
+
}
|
|
110
|
+
createHubServer() {
|
|
111
|
+
const hub = express();
|
|
112
|
+
const createTaskQueueRoute = `/projects/:project_id/locations/:location_id/queues/:queue_name`;
|
|
113
|
+
const createTaskQueueHandler = (req, res) => {
|
|
114
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
115
|
+
const projectId = req.params.project_id;
|
|
116
|
+
const locationId = req.params.location_id;
|
|
117
|
+
const queueName = req.params.queue_name;
|
|
118
|
+
if (!this.validateQueueId(queueName)) {
|
|
119
|
+
res.status(400).send("Invalid Queue ID");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const key = `queue:${projectId}-${locationId}-${queueName}`;
|
|
123
|
+
this.logger.logLabeled("SUCCESS", "tasks", `Created queue with key: ${key}`);
|
|
124
|
+
const body = req.body;
|
|
125
|
+
const taskQueueConfig = {
|
|
126
|
+
retryConfig: {
|
|
127
|
+
maxAttempts: (_b = (_a = body.retryConfig) === null || _a === void 0 ? void 0 : _a.maxAttempts) !== null && _b !== void 0 ? _b : RETRY_CONFIG_DEFAULTS.maxAttempts,
|
|
128
|
+
maxRetrySeconds: (_d = (_c = body.retryConfig) === null || _c === void 0 ? void 0 : _c.maxRetrySeconds) !== null && _d !== void 0 ? _d : RETRY_CONFIG_DEFAULTS.maxRetrySeconds,
|
|
129
|
+
maxBackoffSeconds: (_f = (_e = body.retryConfig) === null || _e === void 0 ? void 0 : _e.maxBackoffSeconds) !== null && _f !== void 0 ? _f : RETRY_CONFIG_DEFAULTS.maxBackoffSeconds,
|
|
130
|
+
maxDoublings: (_h = (_g = body.retryConfig) === null || _g === void 0 ? void 0 : _g.maxDoublings) !== null && _h !== void 0 ? _h : RETRY_CONFIG_DEFAULTS.maxDoublings,
|
|
131
|
+
minBackoffSeconds: (_k = (_j = body.retryConfig) === null || _j === void 0 ? void 0 : _j.minBackoffSeconds) !== null && _k !== void 0 ? _k : RETRY_CONFIG_DEFAULTS.minBackoffSeconds,
|
|
132
|
+
},
|
|
133
|
+
rateLimits: {
|
|
134
|
+
maxConcurrentDispatches: (_m = (_l = body.rateLimits) === null || _l === void 0 ? void 0 : _l.maxConcurrentDispatches) !== null && _m !== void 0 ? _m : RATE_LIMITS_DEFAULT.maxConcurrentDispatches,
|
|
135
|
+
maxDispatchesPerSecond: (_p = (_o = body.rateLimits) === null || _o === void 0 ? void 0 : _o.maxDispatchesPerSecond) !== null && _p !== void 0 ? _p : RATE_LIMITS_DEFAULT.maxDispatchesPerSecond,
|
|
136
|
+
},
|
|
137
|
+
timeoutSeconds: (_q = body.timeoutSeconds) !== null && _q !== void 0 ? _q : 10,
|
|
138
|
+
retry: (_r = body.retry) !== null && _r !== void 0 ? _r : false,
|
|
139
|
+
defaultUri: body.defaultUri,
|
|
140
|
+
};
|
|
141
|
+
if (taskQueueConfig.rateLimits.maxConcurrentDispatches > 5000) {
|
|
142
|
+
res.status(400).send("cannot set maxConcurrentDispatches to a value over 5000");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.controller.createQueue(key, taskQueueConfig);
|
|
146
|
+
this.logger.log("DEBUG", `Created task queue ${key} with configuration: ${JSON.stringify(taskQueueConfig)}`);
|
|
147
|
+
res.status(200).send({ taskQueueConfig });
|
|
148
|
+
};
|
|
149
|
+
const enqueueTasksRoute = `/projects/:project_id/locations/:location_id/queues/:queue_name/tasks`;
|
|
150
|
+
const enqueueTasksHandler = (req, res) => {
|
|
151
|
+
var _a;
|
|
152
|
+
if (!this.controller.isRunning()) {
|
|
153
|
+
this.controller.start();
|
|
154
|
+
}
|
|
155
|
+
const projectId = req.params.project_id;
|
|
156
|
+
const locationId = req.params.location_id;
|
|
157
|
+
const queueName = req.params.queue_name;
|
|
158
|
+
const queueKey = `queue:${projectId}-${locationId}-${queueName}`;
|
|
159
|
+
if (!this.controller.queues[queueKey]) {
|
|
160
|
+
this.logger.log("WARN", "Tried to queue a task into a non-existent queue");
|
|
161
|
+
res.status(404).send("Tried to queue a task from a non-existent queue");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
req.body.task.name =
|
|
165
|
+
(_a = req.body.task.name) !== null && _a !== void 0 ? _a : `/projects/${projectId}/locations/${locationId}/queues/${queueName}/tasks/${Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)}`;
|
|
166
|
+
req.body.task.httpRequest.body = JSON.parse(atob(req.body.task.httpRequest.body));
|
|
167
|
+
const task = req.body.task;
|
|
168
|
+
try {
|
|
169
|
+
this.controller.enqueue(queueKey, task);
|
|
170
|
+
this.logger.log("DEBUG", `Enqueueing task ${task.name} onto ${queueKey}`);
|
|
171
|
+
res.status(200).send({ task: task });
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
res.status(409).send("A task with the same name already exists");
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const deleteTasksRoute = `/projects/:project_id/locations/:location_id/queues/:queue_name/tasks/:task_id`;
|
|
178
|
+
const deleteTasksHandler = (req, res) => {
|
|
179
|
+
const projectId = req.params.project_id;
|
|
180
|
+
const locationId = req.params.location_id;
|
|
181
|
+
const queueName = req.params.queue_name;
|
|
182
|
+
const taskId = req.params.task_id;
|
|
183
|
+
const queueKey = `queue:${projectId}-${locationId}-${queueName}`;
|
|
184
|
+
if (!this.controller.queues[queueKey]) {
|
|
185
|
+
this.logger.log("WARN", "Tried to remove a task from a non-existent queue");
|
|
186
|
+
res.status(404).send("Tried to remove a task from a non-existent queue");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const taskName = `projects/${projectId}/locations/${locationId}/queues/${queueName}/tasks/${taskId}`;
|
|
191
|
+
this.logger.log("DEBUG", `removing: ${taskName}`);
|
|
192
|
+
this.controller.delete(queueKey, taskName);
|
|
193
|
+
res.status(200).send({ res: "OK" });
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
this.logger.log("WARN", "Tried to remove a task that doesn't exist");
|
|
197
|
+
res.status(404).send("Tried to remove a task that doesn't exist");
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const getStatsRoute = `/queueStats`;
|
|
201
|
+
const getStatsHandler = (req, res) => {
|
|
202
|
+
res.json(this.controller.getStatistics());
|
|
203
|
+
};
|
|
204
|
+
hub.get([getStatsRoute], cors({ origin: true }), getStatsHandler);
|
|
205
|
+
hub.post([createTaskQueueRoute], express.json(), createTaskQueueHandler);
|
|
206
|
+
hub.post([enqueueTasksRoute], express.json(), enqueueTasksHandler);
|
|
207
|
+
hub.delete([deleteTasksRoute], express.json(), deleteTasksHandler);
|
|
208
|
+
return hub;
|
|
209
|
+
}
|
|
210
|
+
async start() {
|
|
211
|
+
const { host, port } = this.getInfo();
|
|
212
|
+
const server = this.createHubServer().listen(port, host);
|
|
213
|
+
this.destroyServer = (0, utils_1.createDestroyer)(server);
|
|
214
|
+
return Promise.resolve();
|
|
215
|
+
}
|
|
216
|
+
async connect() {
|
|
217
|
+
return Promise.resolve();
|
|
218
|
+
}
|
|
219
|
+
async stop() {
|
|
220
|
+
if (this.destroyServer) {
|
|
221
|
+
await this.destroyServer();
|
|
222
|
+
}
|
|
223
|
+
this.controller.stop();
|
|
224
|
+
}
|
|
225
|
+
getInfo() {
|
|
226
|
+
const host = this.args.host || constants_1.Constants.getDefaultHost();
|
|
227
|
+
const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.TASKS);
|
|
228
|
+
return {
|
|
229
|
+
name: this.getName(),
|
|
230
|
+
host,
|
|
231
|
+
port,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
getName() {
|
|
235
|
+
return types_1.Emulators.TASKS;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
exports.TasksEmulator = TasksEmulator;
|
package/lib/emulator/types.js
CHANGED
|
@@ -16,6 +16,7 @@ var Emulators;
|
|
|
16
16
|
Emulators["EXTENSIONS"] = "extensions";
|
|
17
17
|
Emulators["EVENTARC"] = "eventarc";
|
|
18
18
|
Emulators["DATACONNECT"] = "dataconnect";
|
|
19
|
+
Emulators["TASKS"] = "tasks";
|
|
19
20
|
})(Emulators = exports.Emulators || (exports.Emulators = {}));
|
|
20
21
|
exports.DOWNLOADABLE_EMULATORS = [
|
|
21
22
|
Emulators.FIRESTORE,
|
|
@@ -41,6 +42,7 @@ exports.ALL_SERVICE_EMULATORS = [
|
|
|
41
42
|
Emulators.STORAGE,
|
|
42
43
|
Emulators.EVENTARC,
|
|
43
44
|
Emulators.DATACONNECT,
|
|
45
|
+
Emulators.TASKS,
|
|
44
46
|
].filter((v) => v);
|
|
45
47
|
exports.EMULATORS_SUPPORTED_BY_FUNCTIONS = [
|
|
46
48
|
Emulators.FIRESTORE,
|
|
@@ -48,6 +50,7 @@ exports.EMULATORS_SUPPORTED_BY_FUNCTIONS = [
|
|
|
48
50
|
Emulators.PUBSUB,
|
|
49
51
|
Emulators.STORAGE,
|
|
50
52
|
Emulators.EVENTARC,
|
|
53
|
+
Emulators.TASKS,
|
|
51
54
|
];
|
|
52
55
|
exports.EMULATORS_SUPPORTED_BY_UI = [
|
|
53
56
|
Emulators.AUTH,
|
|
@@ -179,7 +179,10 @@ async function promptForService(setup, info) {
|
|
|
179
179
|
info.schemaGql = choice.schema.source.files;
|
|
180
180
|
}
|
|
181
181
|
info.cloudSqlDatabase = (_d = (_c = choice.schema.primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
|
|
182
|
-
const connectors = await (0, client_1.listConnectors)(choice.service.name
|
|
182
|
+
const connectors = await (0, client_1.listConnectors)(choice.service.name, [
|
|
183
|
+
"connectors.name",
|
|
184
|
+
"connectors.source.files",
|
|
185
|
+
]);
|
|
183
186
|
if (connectors.length) {
|
|
184
187
|
info.connectors = connectors.map((c) => {
|
|
185
188
|
const id = c.name.split("/").pop();
|
package/package.json
CHANGED
|
@@ -500,6 +500,18 @@
|
|
|
500
500
|
},
|
|
501
501
|
"type": "object"
|
|
502
502
|
},
|
|
503
|
+
"tasks": {
|
|
504
|
+
"additionalProperties": false,
|
|
505
|
+
"properties": {
|
|
506
|
+
"host": {
|
|
507
|
+
"type": "string"
|
|
508
|
+
},
|
|
509
|
+
"port": {
|
|
510
|
+
"type": "number"
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
"type": "object"
|
|
514
|
+
},
|
|
503
515
|
"ui": {
|
|
504
516
|
"additionalProperties": false,
|
|
505
517
|
"properties": {
|