@kapeta/local-cluster-service 0.12.1 → 0.14.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/CHANGELOG.md +14 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/src/api.d.ts +3 -0
- package/dist/cjs/src/api.js +22 -0
- package/dist/cjs/src/assetManager.d.ts +3 -1
- package/dist/cjs/src/assetManager.js +20 -4
- package/dist/cjs/src/assets/routes.js +23 -2
- package/dist/cjs/src/containerManager.js +130 -109
- package/dist/cjs/src/instanceManager.d.ts +4 -3
- package/dist/cjs/src/instanceManager.js +80 -59
- package/dist/cjs/src/instances/routes.js +19 -11
- package/dist/cjs/src/operatorManager.d.ts +5 -3
- package/dist/cjs/src/operatorManager.js +34 -23
- package/dist/cjs/src/providerManager.js +1 -1
- package/dist/cjs/src/repositoryManager.d.ts +2 -4
- package/dist/cjs/src/repositoryManager.js +51 -66
- package/dist/cjs/src/socketManager.js +1 -1
- package/dist/cjs/src/taskManager.d.ts +64 -0
- package/dist/cjs/src/taskManager.js +161 -0
- package/dist/cjs/src/tasks/routes.d.ts +3 -0
- package/dist/cjs/src/tasks/routes.js +35 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/src/api.d.ts +3 -0
- package/dist/esm/src/api.js +17 -0
- package/dist/esm/src/assetManager.d.ts +3 -1
- package/dist/esm/src/assetManager.js +20 -4
- package/dist/esm/src/assets/routes.js +23 -2
- package/dist/esm/src/containerManager.js +130 -109
- package/dist/esm/src/instanceManager.d.ts +4 -3
- package/dist/esm/src/instanceManager.js +80 -59
- package/dist/esm/src/instances/routes.js +19 -11
- package/dist/esm/src/operatorManager.d.ts +5 -3
- package/dist/esm/src/operatorManager.js +34 -23
- package/dist/esm/src/providerManager.js +1 -1
- package/dist/esm/src/repositoryManager.d.ts +2 -4
- package/dist/esm/src/repositoryManager.js +51 -66
- package/dist/esm/src/socketManager.js +1 -1
- package/dist/esm/src/taskManager.d.ts +64 -0
- package/dist/esm/src/taskManager.js +157 -0
- package/dist/esm/src/tasks/routes.d.ts +3 -0
- package/dist/esm/src/tasks/routes.js +30 -0
- package/index.ts +9 -0
- package/package.json +2 -1
- package/src/api.ts +21 -0
- package/src/assetManager.ts +28 -4
- package/src/assets/routes.ts +24 -2
- package/src/containerManager.ts +151 -126
- package/src/instanceManager.ts +106 -70
- package/src/instances/routes.ts +18 -12
- package/src/operatorManager.ts +46 -28
- package/src/providerManager.ts +1 -1
- package/src/repositoryManager.ts +65 -63
- package/src/socketManager.ts +1 -1
- package/src/taskManager.ts +223 -0
- package/src/tasks/routes.ts +38 -0
- package/src/utils/BlockInstanceRunner.ts +0 -2
@@ -10,6 +10,7 @@ const cors_1 = require("../middleware/cors");
|
|
10
10
|
const kapeta_1 = require("../middleware/kapeta");
|
11
11
|
const stringBody_1 = require("../middleware/stringBody");
|
12
12
|
const types_1 = require("../types");
|
13
|
+
const taskManager_1 = require("../taskManager");
|
13
14
|
const router = (0, express_promise_router_1.default)();
|
14
15
|
router.use('/', cors_1.corsHandler);
|
15
16
|
router.use('/', kapeta_1.kapetaHeaders);
|
@@ -29,33 +30,40 @@ router.get('/:systemId/instances', (req, res) => {
|
|
29
30
|
* Start all instances in a plan
|
30
31
|
*/
|
31
32
|
router.post('/:systemId/start', async (req, res) => {
|
32
|
-
const
|
33
|
+
const task = await instanceManager_1.instanceManager.startAllForPlan(req.params.systemId);
|
33
34
|
res.status(202).send({
|
34
35
|
ok: true,
|
35
|
-
|
36
|
-
return { pid: p.pid, type: p.type };
|
37
|
-
}),
|
36
|
+
taskId: task.id,
|
38
37
|
});
|
39
38
|
});
|
40
39
|
/**
|
41
40
|
* Stop all instances in plan
|
42
41
|
*/
|
43
42
|
router.post('/:systemId/stop', async (req, res) => {
|
44
|
-
|
43
|
+
const task = instanceManager_1.instanceManager.stopAllForPlan(req.params.systemId);
|
45
44
|
res.status(202).send({
|
46
45
|
ok: true,
|
46
|
+
taskId: task.id,
|
47
47
|
});
|
48
48
|
});
|
49
49
|
/**
|
50
50
|
* Start single instance in a plan
|
51
51
|
*/
|
52
52
|
router.post('/:systemId/:instanceId/start', async (req, res) => {
|
53
|
-
const
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
const result = await instanceManager_1.instanceManager.start(req.params.systemId, req.params.instanceId);
|
54
|
+
if (result instanceof taskManager_1.Task) {
|
55
|
+
res.status(202).send({
|
56
|
+
ok: true,
|
57
|
+
taskId: result.id,
|
58
|
+
});
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
res.status(202).send({
|
62
|
+
ok: true,
|
63
|
+
pid: result.pid,
|
64
|
+
type: result.type,
|
65
|
+
});
|
66
|
+
}
|
59
67
|
});
|
60
68
|
/**
|
61
69
|
* Stop single instance in a plan
|
@@ -1,10 +1,12 @@
|
|
1
|
+
import { DefinitionInfo } from '@kapeta/local-cluster-config';
|
1
2
|
import { ContainerInfo } from './containerManager';
|
2
3
|
import { EnvironmentType, OperatorInfo } from './types';
|
3
4
|
export declare const KIND_OPERATOR = "core/resource-type-operator";
|
4
5
|
declare class Operator {
|
5
|
-
private _data;
|
6
|
-
constructor(data:
|
7
|
-
|
6
|
+
private readonly _data;
|
7
|
+
constructor(data: DefinitionInfo);
|
8
|
+
getLocalData(): any;
|
9
|
+
getDefinitionInfo(): DefinitionInfo;
|
8
10
|
getCredentials(): any;
|
9
11
|
}
|
10
12
|
declare class OperatorManager {
|
@@ -15,17 +15,21 @@ const definitionsManager_1 = require("./definitionsManager");
|
|
15
15
|
const utils_1 = require("./utils/utils");
|
16
16
|
const lodash_1 = __importDefault(require("lodash"));
|
17
17
|
const async_lock_1 = __importDefault(require("async-lock"));
|
18
|
+
const taskManager_1 = require("./taskManager");
|
18
19
|
exports.KIND_OPERATOR = 'core/resource-type-operator';
|
19
20
|
class Operator {
|
20
21
|
_data;
|
21
22
|
constructor(data) {
|
22
23
|
this._data = data;
|
23
24
|
}
|
24
|
-
|
25
|
+
getLocalData() {
|
26
|
+
return this._data.definition.spec.local;
|
27
|
+
}
|
28
|
+
getDefinitionInfo() {
|
25
29
|
return this._data;
|
26
30
|
}
|
27
31
|
getCredentials() {
|
28
|
-
return this._data.credentials;
|
32
|
+
return this._data.definition.spec.local.credentials;
|
29
33
|
}
|
30
34
|
}
|
31
35
|
class OperatorManager {
|
@@ -58,7 +62,7 @@ class OperatorManager {
|
|
58
62
|
if (!operator.definition.spec || !operator.definition.spec.local) {
|
59
63
|
throw new Error(`Operator missing local definition: ${resourceType}:${version}`);
|
60
64
|
}
|
61
|
-
return new Operator(operator
|
65
|
+
return new Operator(operator);
|
62
66
|
}
|
63
67
|
/**
|
64
68
|
* Get information about a specific consumed resource
|
@@ -119,7 +123,7 @@ class OperatorManager {
|
|
119
123
|
const key = `${systemId}#${resourceType}:${version}`;
|
120
124
|
return await this.operatorLock.acquire(key, async () => {
|
121
125
|
const operator = this.getOperator(resourceType, version);
|
122
|
-
const operatorData = operator.
|
126
|
+
const operatorData = operator.getLocalData();
|
123
127
|
const portTypes = Object.keys(operatorData.ports);
|
124
128
|
portTypes.sort();
|
125
129
|
const ports = {};
|
@@ -147,6 +151,7 @@ class OperatorManager {
|
|
147
151
|
const Labels = {
|
148
152
|
kapeta: 'true',
|
149
153
|
};
|
154
|
+
const operatorMetadata = operator.getDefinitionInfo().definition.metadata;
|
150
155
|
const bindHost = (0, utils_1.getBindHost)();
|
151
156
|
const ExposedPorts = {};
|
152
157
|
lodash_1.default.forEach(ports, (portInfo, containerPort) => {
|
@@ -163,26 +168,32 @@ class OperatorManager {
|
|
163
168
|
lodash_1.default.forEach(operatorData.env, (value, name) => {
|
164
169
|
Env.push(name + '=' + value);
|
165
170
|
});
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
171
|
+
const task = taskManager_1.taskManager.add(`operator:ensure:${key}`, async () => {
|
172
|
+
let HealthCheck = undefined;
|
173
|
+
if (operatorData.health) {
|
174
|
+
HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
|
175
|
+
}
|
176
|
+
const container = await containerManager_1.containerManager.ensureContainer({
|
177
|
+
name: containerName,
|
178
|
+
Image: operatorData.image,
|
179
|
+
Hostname: containerName + '.kapeta',
|
180
|
+
Labels,
|
181
|
+
Cmd: operatorData.cmd,
|
182
|
+
ExposedPorts,
|
183
|
+
Env,
|
184
|
+
HealthCheck,
|
185
|
+
HostConfig: {
|
186
|
+
PortBindings,
|
187
|
+
Mounts,
|
188
|
+
},
|
189
|
+
});
|
190
|
+
await containerManager_1.containerManager.waitForReady(container);
|
191
|
+
return new containerManager_1.ContainerInfo(container);
|
192
|
+
}, {
|
193
|
+
name: `Ensuring ${operatorMetadata.title ?? operatorMetadata.name}`,
|
194
|
+
systemId,
|
183
195
|
});
|
184
|
-
|
185
|
-
return new containerManager_1.ContainerInfo(container);
|
196
|
+
return task.wait();
|
186
197
|
});
|
187
198
|
}
|
188
199
|
}
|
@@ -22,7 +22,7 @@ class ProviderManager {
|
|
22
22
|
if (this._webAssetCache[id] && (await fs_extra_1.default.pathExists(this._webAssetCache[id]))) {
|
23
23
|
return fs_extra_1.default.readFile(this._webAssetCache[id], 'utf8');
|
24
24
|
}
|
25
|
-
await repositoryManager_1.repositoryManager.ensureAsset(handle, name, version);
|
25
|
+
await repositoryManager_1.repositoryManager.ensureAsset(handle, name, version, true);
|
26
26
|
const installedProvider = this.getWebProviders().find((providerDefinition) => {
|
27
27
|
return providerDefinition.definition.metadata.name === fullName && providerDefinition.version === version;
|
28
28
|
});
|
@@ -1,17 +1,15 @@
|
|
1
|
+
import { Task } from './taskManager';
|
1
2
|
declare class RepositoryManager {
|
2
3
|
private changeEventsEnabled;
|
3
4
|
private _registryService;
|
4
5
|
private _cache;
|
5
6
|
private watcher?;
|
6
|
-
private _installQueue;
|
7
|
-
private _processing;
|
8
7
|
constructor();
|
9
8
|
setChangeEventsEnabled(enabled: boolean): void;
|
10
9
|
listenForChanges(): void;
|
11
10
|
stopListening(): void;
|
12
11
|
private _install;
|
13
|
-
|
14
|
-
ensureAsset(handle: string, name: string, version: string): Promise<void>;
|
12
|
+
ensureAsset(handle: string, name: string, version: string, wait?: boolean): Promise<undefined | Task[]>;
|
15
13
|
}
|
16
14
|
export declare const repositoryManager: RepositoryManager;
|
17
15
|
export {};
|
@@ -15,20 +15,20 @@ const socketManager_1 = require("./socketManager");
|
|
15
15
|
const progressListener_1 = require("./progressListener");
|
16
16
|
const nodejs_registry_utils_1 = require("@kapeta/nodejs-registry-utils");
|
17
17
|
const definitionsManager_1 = require("./definitionsManager");
|
18
|
+
const taskManager_1 = require("./taskManager");
|
19
|
+
const utils_1 = require("./utils/utils");
|
20
|
+
const assetManager_1 = require("./assetManager");
|
18
21
|
const INSTALL_ATTEMPTED = {};
|
19
22
|
class RepositoryManager {
|
20
23
|
changeEventsEnabled;
|
21
24
|
_registryService;
|
22
25
|
_cache;
|
23
26
|
watcher;
|
24
|
-
_installQueue;
|
25
|
-
_processing = false;
|
26
27
|
constructor() {
|
27
28
|
this.changeEventsEnabled = true;
|
28
29
|
this.listenForChanges();
|
29
30
|
this._registryService = new nodejs_registry_utils_1.RegistryService(nodejs_registry_utils_1.Config.data.registry.url);
|
30
31
|
this._cache = {};
|
31
|
-
this._installQueue = [];
|
32
32
|
}
|
33
33
|
setChangeEventsEnabled(enabled) {
|
34
34
|
this.changeEventsEnabled = enabled;
|
@@ -109,74 +109,54 @@ class RepositoryManager {
|
|
109
109
|
}
|
110
110
|
async _install(refs) {
|
111
111
|
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
112
|
-
const
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
const filteredRefs = normalizedRefs
|
117
|
-
.filter((ref) => !INSTALL_ATTEMPTED[ref])
|
118
|
-
.filter((ref) => !definitionsManager_1.definitionsManager.exists(ref));
|
119
|
-
if (filteredRefs.length > 0) {
|
120
|
-
console.log(`Auto-installing dependencies: ${filteredRefs.join(', ')}`);
|
121
|
-
filteredRefs.forEach((ref) => (INSTALL_ATTEMPTED[ref] = true));
|
122
|
-
//Auto-install missing asset
|
123
|
-
try {
|
124
|
-
//We change to a temp dir to avoid issues with the current working directory
|
125
|
-
process.chdir(node_os_1.default.tmpdir());
|
126
|
-
//Disable change events while installing
|
127
|
-
this.setChangeEventsEnabled(false);
|
128
|
-
socketManager_1.socketManager.emit(`install`, 'install:action', {
|
129
|
-
type: 'start',
|
130
|
-
refs,
|
131
|
-
});
|
132
|
-
await nodejs_registry_utils_1.Actions.install(progressListener_1.progressListener, normalizedRefs, {});
|
133
|
-
socketManager_1.socketManager.emit(`install`, 'install:action', {
|
134
|
-
type: 'done',
|
135
|
-
refs,
|
136
|
-
});
|
137
|
-
}
|
138
|
-
catch (e) {
|
139
|
-
socketManager_1.socketManager.emit(`install`, 'install:action', {
|
140
|
-
type: 'failed',
|
141
|
-
refs,
|
142
|
-
error: e.message,
|
143
|
-
});
|
144
|
-
}
|
145
|
-
finally {
|
146
|
-
this.setChangeEventsEnabled(true);
|
147
|
-
}
|
148
|
-
}
|
149
|
-
resolve();
|
112
|
+
const createInstaller = (ref) => {
|
113
|
+
return async () => {
|
114
|
+
if (INSTALL_ATTEMPTED[ref]) {
|
115
|
+
return;
|
150
116
|
}
|
151
|
-
|
152
|
-
|
117
|
+
if (definitionsManager_1.definitionsManager.exists(ref)) {
|
118
|
+
return;
|
153
119
|
}
|
154
|
-
|
155
|
-
|
120
|
+
console.log(`Installing asset: ${ref}`);
|
121
|
+
INSTALL_ATTEMPTED[ref] = true;
|
122
|
+
//Auto-install missing asset
|
123
|
+
try {
|
124
|
+
//We change to a temp dir to avoid issues with the current working directory
|
125
|
+
process.chdir(node_os_1.default.tmpdir());
|
126
|
+
//Disable change events while installing
|
127
|
+
this.setChangeEventsEnabled(false);
|
128
|
+
await nodejs_registry_utils_1.Actions.install(progressListener_1.progressListener, [ref], {});
|
156
129
|
}
|
157
|
-
|
158
|
-
|
159
|
-
this._processNext().catch((e) => console.error(e));
|
160
|
-
return out;
|
161
|
-
}
|
162
|
-
async _processNext() {
|
163
|
-
if (this._processing) {
|
164
|
-
return;
|
165
|
-
}
|
166
|
-
this._processing = true;
|
167
|
-
try {
|
168
|
-
while (this._installQueue.length > 0) {
|
169
|
-
const item = this._installQueue.shift();
|
170
|
-
if (item) {
|
171
|
-
await item();
|
130
|
+
finally {
|
131
|
+
this.setChangeEventsEnabled(true);
|
172
132
|
}
|
133
|
+
definitionsManager_1.definitionsManager.clearCache();
|
134
|
+
assetManager_1.assetManager.clearCache();
|
135
|
+
console.log(`Asset installed: ${ref}`);
|
136
|
+
};
|
137
|
+
};
|
138
|
+
const tasks = [];
|
139
|
+
while (refs.length > 0) {
|
140
|
+
let ref = refs.shift();
|
141
|
+
if (!ref) {
|
142
|
+
continue;
|
173
143
|
}
|
144
|
+
ref = (0, utils_1.normalizeKapetaUri)(ref);
|
145
|
+
if (INSTALL_ATTEMPTED[ref]) {
|
146
|
+
continue;
|
147
|
+
}
|
148
|
+
if (definitionsManager_1.definitionsManager.exists(ref)) {
|
149
|
+
continue;
|
150
|
+
}
|
151
|
+
const task = taskManager_1.taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
|
152
|
+
name: `Installing ${ref}`,
|
153
|
+
group: 'asset:install:',
|
154
|
+
});
|
155
|
+
tasks.push(task);
|
174
156
|
}
|
175
|
-
|
176
|
-
this._processing = false;
|
177
|
-
}
|
157
|
+
return tasks;
|
178
158
|
}
|
179
|
-
async ensureAsset(handle, name, version) {
|
159
|
+
async ensureAsset(handle, name, version, wait = true) {
|
180
160
|
const fullName = `${handle}/${name}`;
|
181
161
|
const ref = `${fullName}:${version}`;
|
182
162
|
if (version === 'local') {
|
@@ -207,16 +187,21 @@ class RepositoryManager {
|
|
207
187
|
throw e;
|
208
188
|
}
|
209
189
|
this._cache[ref] = true;
|
190
|
+
let tasks = undefined;
|
210
191
|
if (!installedAsset) {
|
211
|
-
await this._install([ref]);
|
192
|
+
tasks = await this._install([ref]);
|
212
193
|
}
|
213
194
|
else {
|
214
195
|
//Ensure dependencies are installed
|
215
196
|
const refs = assetVersion.dependencies.map((dep) => dep.name);
|
216
197
|
if (refs.length > 0) {
|
217
|
-
await this._install(refs);
|
198
|
+
tasks = await this._install(refs);
|
218
199
|
}
|
219
200
|
}
|
201
|
+
if (tasks && wait) {
|
202
|
+
await Promise.all(tasks.map((t) => t.future.promise));
|
203
|
+
}
|
204
|
+
return tasks;
|
220
205
|
}
|
221
206
|
}
|
222
207
|
exports.repositoryManager = new RepositoryManager();
|
@@ -30,7 +30,7 @@ class SocketManager {
|
|
30
30
|
this.io.to(context).emit(type, { context, payload });
|
31
31
|
}
|
32
32
|
emitGlobal(type, payload) {
|
33
|
-
this.io.emit(type,
|
33
|
+
this.io.emit(type, payload);
|
34
34
|
}
|
35
35
|
_bindIO() {
|
36
36
|
this.io.on('connection', (socket) => this._handleSocketCreated(socket));
|
@@ -0,0 +1,64 @@
|
|
1
|
+
export type TaskRunner<T> = (task: Task<T>) => Promise<T>;
|
2
|
+
export declare enum TaskStatus {
|
3
|
+
PENDING = "PENDING",
|
4
|
+
RUNNING = "RUNNING",
|
5
|
+
COMPLETED = "COMPLETED",
|
6
|
+
FAILED = "FAILED"
|
7
|
+
}
|
8
|
+
interface Future<T = void> {
|
9
|
+
promise: Promise<T>;
|
10
|
+
resolve: (result: T) => void;
|
11
|
+
reject: (e: any) => void;
|
12
|
+
}
|
13
|
+
interface TaskMetadata {
|
14
|
+
name: string;
|
15
|
+
/**
|
16
|
+
* A unique prefix for the task. If defined only 1 task with this ID prefix will be executed at a time
|
17
|
+
*/
|
18
|
+
group?: string;
|
19
|
+
progress?: number;
|
20
|
+
[key: string]: any;
|
21
|
+
}
|
22
|
+
interface TaskData<T = void> {
|
23
|
+
id: string;
|
24
|
+
status: TaskStatus;
|
25
|
+
errorMessage?: string;
|
26
|
+
metadata: TaskMetadata;
|
27
|
+
future: Future<T>;
|
28
|
+
run: TaskRunner<T>;
|
29
|
+
}
|
30
|
+
export declare class Task<T = void> implements TaskData<T> {
|
31
|
+
private data;
|
32
|
+
constructor(task: TaskData<T>);
|
33
|
+
get id(): string;
|
34
|
+
get status(): TaskStatus;
|
35
|
+
get errorMessage(): string | undefined;
|
36
|
+
get metadata(): TaskMetadata;
|
37
|
+
get future(): Future<T>;
|
38
|
+
get run(): TaskRunner<T>;
|
39
|
+
set status(status: TaskStatus);
|
40
|
+
set errorMessage(errorMessage: string | undefined);
|
41
|
+
set metadata(metadata: TaskMetadata);
|
42
|
+
emitUpdate(): void;
|
43
|
+
wait(): Promise<T>;
|
44
|
+
toData(): {
|
45
|
+
id: string;
|
46
|
+
status: TaskStatus;
|
47
|
+
errorMessage?: string | undefined;
|
48
|
+
metadata: TaskMetadata;
|
49
|
+
future: Future<T>;
|
50
|
+
run: TaskRunner<T>;
|
51
|
+
};
|
52
|
+
}
|
53
|
+
declare class TaskManager {
|
54
|
+
private _tasks;
|
55
|
+
add<T>(id: string, runner: TaskRunner<T>, metadata: TaskMetadata): Task<T>;
|
56
|
+
waitFor(filter: (task: Task<any>) => boolean): Promise<void>;
|
57
|
+
get(taskId: string): Task<any> | undefined;
|
58
|
+
exists(taskId: string): boolean;
|
59
|
+
remove(taskId: string): void;
|
60
|
+
list(): TaskData[];
|
61
|
+
private invokeTask;
|
62
|
+
}
|
63
|
+
export declare const taskManager: TaskManager;
|
64
|
+
export {};
|
@@ -0,0 +1,161 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.taskManager = exports.Task = exports.TaskStatus = void 0;
|
4
|
+
/**
|
5
|
+
* Class that handles processing background tasks.
|
6
|
+
*/
|
7
|
+
const socketManager_1 = require("./socketManager");
|
8
|
+
const EVENT_TASK_UPDATED = 'task-updated';
|
9
|
+
const EVENT_TASK_ADDED = 'task-added';
|
10
|
+
const EVENT_TASK_REMOVED = 'task-removed';
|
11
|
+
var TaskStatus;
|
12
|
+
(function (TaskStatus) {
|
13
|
+
TaskStatus["PENDING"] = "PENDING";
|
14
|
+
TaskStatus["RUNNING"] = "RUNNING";
|
15
|
+
TaskStatus["COMPLETED"] = "COMPLETED";
|
16
|
+
TaskStatus["FAILED"] = "FAILED";
|
17
|
+
})(TaskStatus || (exports.TaskStatus = TaskStatus = {}));
|
18
|
+
class Task {
|
19
|
+
data;
|
20
|
+
constructor(task) {
|
21
|
+
this.data = task;
|
22
|
+
}
|
23
|
+
get id() {
|
24
|
+
return this.data.id;
|
25
|
+
}
|
26
|
+
get status() {
|
27
|
+
return this.data.status;
|
28
|
+
}
|
29
|
+
get errorMessage() {
|
30
|
+
return this.data.errorMessage;
|
31
|
+
}
|
32
|
+
get metadata() {
|
33
|
+
return this.data.metadata;
|
34
|
+
}
|
35
|
+
get future() {
|
36
|
+
return this.data.future;
|
37
|
+
}
|
38
|
+
get run() {
|
39
|
+
return this.data.run;
|
40
|
+
}
|
41
|
+
set status(status) {
|
42
|
+
this.data.status = status;
|
43
|
+
}
|
44
|
+
set errorMessage(errorMessage) {
|
45
|
+
this.data.errorMessage = errorMessage;
|
46
|
+
}
|
47
|
+
set metadata(metadata) {
|
48
|
+
this.data.metadata = metadata;
|
49
|
+
}
|
50
|
+
emitUpdate() {
|
51
|
+
socketManager_1.socketManager.emitGlobal(EVENT_TASK_UPDATED, this.toData());
|
52
|
+
}
|
53
|
+
async wait() {
|
54
|
+
return this.future.promise;
|
55
|
+
}
|
56
|
+
toData() {
|
57
|
+
return { ...this.data };
|
58
|
+
}
|
59
|
+
}
|
60
|
+
exports.Task = Task;
|
61
|
+
function createFuture() {
|
62
|
+
let resolve = () => { };
|
63
|
+
let reject = () => { };
|
64
|
+
const promise = new Promise((res, rej) => {
|
65
|
+
resolve = res;
|
66
|
+
reject = rej;
|
67
|
+
});
|
68
|
+
return {
|
69
|
+
promise,
|
70
|
+
resolve,
|
71
|
+
reject,
|
72
|
+
};
|
73
|
+
}
|
74
|
+
class TaskManager {
|
75
|
+
_tasks = [];
|
76
|
+
add(id, runner, metadata) {
|
77
|
+
const existingTask = this.get(id);
|
78
|
+
if (existingTask) {
|
79
|
+
return existingTask;
|
80
|
+
}
|
81
|
+
const future = createFuture();
|
82
|
+
const task = new Task({
|
83
|
+
id,
|
84
|
+
status: TaskStatus.PENDING,
|
85
|
+
metadata,
|
86
|
+
future,
|
87
|
+
run: runner,
|
88
|
+
});
|
89
|
+
this._tasks.push(task);
|
90
|
+
socketManager_1.socketManager.emitGlobal(EVENT_TASK_ADDED, task.toData());
|
91
|
+
this.invokeTask(task).catch(() => { });
|
92
|
+
return task;
|
93
|
+
}
|
94
|
+
async waitFor(filter) {
|
95
|
+
const tasks = this._tasks.filter(filter);
|
96
|
+
while (tasks.length > 0) {
|
97
|
+
const task = tasks.shift();
|
98
|
+
if (!task) {
|
99
|
+
continue;
|
100
|
+
}
|
101
|
+
try {
|
102
|
+
await task.wait();
|
103
|
+
}
|
104
|
+
catch (e) {
|
105
|
+
//Ignore
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
get(taskId) {
|
110
|
+
return this._tasks.find((t) => t.id === taskId);
|
111
|
+
}
|
112
|
+
exists(taskId) {
|
113
|
+
return !!this.get(taskId);
|
114
|
+
}
|
115
|
+
remove(taskId) {
|
116
|
+
const task = this.get(taskId);
|
117
|
+
if (!task) {
|
118
|
+
return;
|
119
|
+
}
|
120
|
+
if (task.status === TaskStatus.RUNNING) {
|
121
|
+
throw new Error('Cannot remove a running task');
|
122
|
+
}
|
123
|
+
this._tasks = this._tasks.filter((t) => t.id !== taskId);
|
124
|
+
socketManager_1.socketManager.emitGlobal(EVENT_TASK_REMOVED, task.toData());
|
125
|
+
}
|
126
|
+
list() {
|
127
|
+
return this._tasks.map((t) => t.toData());
|
128
|
+
}
|
129
|
+
async invokeTask(task) {
|
130
|
+
if (task.metadata.group) {
|
131
|
+
const existingTaskInGroup = this._tasks.find((t) => t.id !== task.id && t.metadata.group === task.metadata.group && t.status === TaskStatus.RUNNING);
|
132
|
+
if (existingTaskInGroup) {
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
}
|
136
|
+
try {
|
137
|
+
task.status = TaskStatus.RUNNING;
|
138
|
+
task.emitUpdate();
|
139
|
+
const result = await task.run(task);
|
140
|
+
task.status = TaskStatus.COMPLETED;
|
141
|
+
task.future.resolve(result);
|
142
|
+
task.emitUpdate();
|
143
|
+
}
|
144
|
+
catch (e) {
|
145
|
+
task.errorMessage = e.message;
|
146
|
+
task.status = TaskStatus.FAILED;
|
147
|
+
task.future.reject(e);
|
148
|
+
task.emitUpdate();
|
149
|
+
}
|
150
|
+
finally {
|
151
|
+
this.remove(task.id);
|
152
|
+
}
|
153
|
+
if (task.metadata.group) {
|
154
|
+
const nextTaskInGroup = this._tasks.find((t) => t.id !== task.id && t.metadata.group === task.metadata.group && t.status === TaskStatus.PENDING);
|
155
|
+
if (nextTaskInGroup) {
|
156
|
+
return this.invokeTask(nextTaskInGroup);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
exports.taskManager = new TaskManager();
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const express_promise_router_1 = __importDefault(require("express-promise-router"));
|
7
|
+
const cors_1 = require("../middleware/cors");
|
8
|
+
const taskManager_1 = require("../taskManager");
|
9
|
+
const router = (0, express_promise_router_1.default)();
|
10
|
+
router.use('/', cors_1.corsHandler);
|
11
|
+
/**
|
12
|
+
* Get all current tasks
|
13
|
+
*/
|
14
|
+
router.get('/', (req, res) => {
|
15
|
+
res.send(taskManager_1.taskManager.list());
|
16
|
+
});
|
17
|
+
router.get('/:taskId', (req, res) => {
|
18
|
+
const task = taskManager_1.taskManager.get(req.params.taskId);
|
19
|
+
if (!task) {
|
20
|
+
res.status(404).send({ error: 'Task not found' });
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
res.send(task.toData());
|
24
|
+
});
|
25
|
+
router.delete('/:taskId', (req, res) => {
|
26
|
+
try {
|
27
|
+
taskManager_1.taskManager.remove(req.params.taskId);
|
28
|
+
res.send({ ok: true });
|
29
|
+
}
|
30
|
+
catch (e) {
|
31
|
+
res.status(400).send({ error: e.message });
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
});
|
35
|
+
exports.default = router;
|
package/dist/esm/index.js
CHANGED
@@ -15,6 +15,8 @@ import FilesystemRoutes from './src/filesystem/routes';
|
|
15
15
|
import AssetsRoutes from './src/assets/routes';
|
16
16
|
import ProviderRoutes from './src/providers/routes';
|
17
17
|
import AttachmentRoutes from './src/attachments/routes';
|
18
|
+
import TaskRoutes from './src/tasks/routes';
|
19
|
+
import APIRoutes from './src/api';
|
18
20
|
import { getBindHost } from './src/utils/utils';
|
19
21
|
import request from 'request';
|
20
22
|
let currentServer = null;
|
@@ -29,6 +31,8 @@ function createServer() {
|
|
29
31
|
app.use('/assets', AssetsRoutes);
|
30
32
|
app.use('/providers', ProviderRoutes);
|
31
33
|
app.use('/attachments', AttachmentRoutes);
|
34
|
+
app.use('/tasks', TaskRoutes);
|
35
|
+
app.use('/api', APIRoutes);
|
32
36
|
app.get('/status', async (req, res) => {
|
33
37
|
res.send({
|
34
38
|
ok: true,
|